Deploy Buffalo App to DigitalOcean

Prerequisites

Introduction

Set Up Toodo Application

git clone --depth=1 https://github.com/gobuffalo/toodo.git $GOPATH/src/github.com/gobuffalo/toodocd  $GOPATH/src/github.com/gobuffalo/toodoyarn installbuffalo buildgit checkout -b do-deploy
// Automatically redirect to SSL
// app.Use(ssl.ForceSSL(secure.Options{
// SSLRedirect: ENV == "production",
// SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
// }))

Install and Verify Docker Compose on DigitalOcean Droplet

sudo mkdir -p /opt/bin

sudo curl -L --fail https://github.com/docker/compose/releases/download/1.21.2/run.sh -o /opt/bin/docker-compose

sudo chmod +x /opt/bin/docker-compose
docker-compose --version

Docker Compose File

version: '3'services:   
db:
image: postgres:10-alpine
container_name: pg_db
environment:
POSTGRES_USER: postgres
POSTGRES_DB: toodo-db
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- 5432:5432
restart: always
# Un-comment the following two lines if you want data to persist across containers
# volumes:
# - pg-data:/var/lib/postgresql/data

toodo-app:
depends_on:
- db
image: toodo
container_name: toodo-app
restart: always
environment:
- SESSION_SECRET=${SESSION_SECRET}
- DATABASE_URL=${DATABASE_URL}
ports:
- 3000:3000
web:
depends_on:
- toodo-app
image: nginx
container_name: nginx
restart: always
ports:
- 80:80
- 443:443
volumes:
pg-data:

Create Environment Variables Script Generator

#!/bin/bash
# create-env.sh
sess_secret=$(uuidgen)
uuid=$(uuidgen)
db_passwd=${uuid:0:12}
echo -e SESSION_SECRET=${sess_secret} > dep.env
echo -e DATABASE_URL=\"postgres://postgres:${db_passwd}@pg_db:5432/toodo-db?sslmode=disable\" >> dep.env;
echo -e POSTGRES_PASSWORD=${db_passwd} >> dep.env;
chmod +x create-env.sh

Create Application Dockerfile

FROM alpine
RUN apk add --no-cache bash && apk add --no-cache ca-certificates && apk add --no-cache postgresql-client
WORKDIR /bin/COPY toodo .
COPY ./wait-for-postgres.sh .
RUN chmod +x wait-for-postgres.sh
ENV GO_ENV=productionENV ADDR=0.0.0.0EXPOSE 3000CMD /bin/wait-for-postgres.sh; /bin/toodo migrate; /bin/toodo

Create Wait For PostgreSQL Startup Script

#!/bin/sh
# wait-for-postgres.sh
# Script lifted from
# Using Docker Compose Entrypoint To Check if Postgres is Running
# https://bit.ly/2KCdFxh
# Author: Kelly Andrews
set -ecmd="$@"# service/container name in the docker-compose file
host="db"
# PostgreSQL port
port="5432"
# Database user making commection
user="postgres"
# pg_isready is a postgreSQL client tool for checking the connection
# status of PostgreSQL server
while ! pg_isready -h ${host} -p ${port} -U ${user} > /dev/null 2> /dev/null; do
echo "Connecting to postgres Failed"
sleep 1
done
>&2 echo "Postgres is up - executing command"
exec $cmd
chmod +x wait-for-postgres.sh

Nginx Dockerfile and Configuration

user  nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main; sendfile on;
#tcp_nopush on;
keepalive_timeout 65; #gzip on; upstream toodo-app_server {
server toodo-app:3000;
}
server {
listen 80;
server_name toodo.deitagy.com;
# Hide NGINX version (security best practice)
server_tokens off;
location / {
proxy_pass http://toodo-app_server;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
FROM nginx:1.15-alpineCOPY nginx.conf /etc/nginx/nginx.conf

Create Build and Upload Script

#!/bin/bash
# build-and-upload.sh
readonly app=toodo
readonly app_image=toodo:latest
readonly app_docker=app.Dockerfile
readonly nginx_image=nginx:latest
readonly nginx_docker=nginx.Dockerfile
readonly bzip_file=${app}-latest.tar.bz2readonly app_key="~/.ssh/toodo-ssh"
readonly app_domain_user="core@toodo.deitagy.com"
clear# Remove image and ignore 'image does not exist' error
echo "Building app and images ..."
docker rmi -f ${app_image} 2>/dev/null
env GOOS=linux GOARCH=386 buffalo build -o ${app}
docker build --no-cache -t ${app_image} -f ${app_docker} .
rm ${app}
docker rmi -f ${nginx_image} 2>/dev/null
docker build --no-cache -t ${nginx_image} -f ${nginx_docker} .
echo "Creating deployment .env file..."
./create-env.sh
echo "saving docker images ..."
docker save ${app_image} ${nginx_image} | bzip2 > ${bzip_file}
ls -lah ${bzip_file}
echo "uploading images to the server ..."
scp -i ${app_key} ${app}-latest.tar.bz2 docker-compose.yml dep.env ${app_domain_user}:/home/core
ssh -i ${app_key} ${app_domain_user} << ENDSSH
cd /home/core
bunzip2 --stdout ${bzip_file} | docker load
rm ${bzip_file}
mv dep.env .env -f
docker-compose down && docker-compose up -d --force-recreate;
ENDSSH
chmod +x build-and-upload.sh

Testing Our Deployment

./build-and-upload.sh

Setup Systemd Service

Resources

--

--

--

Software Developer

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Flutter — Cart Calculator App: Part 4

Smart Websites for Smartphones

Git — The most used technology by developers

Unity2D DevLog 12 — It’s a Trap!

Tips for choosing the right software for your business

Microfrontend — what, why and how

Why you should use Rails as an API

Pulumi end-to-end on Azure, part one

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Kagunda JM

Kagunda JM

Software Developer

More from Medium

An Extremely Basic Introduction into Websockets

HTTP PROTOCOL

WebSocket

Merkel tree