Fabio Berger

How to dockerize a golang webapp with postgres DB

I've been playing around with Docker recently and have decided to use it in the deployment of a Golang Negroni + Postgres webapp I've been working on. In order to ensure future scalability and to comply with best practices, this meant spinning up two docker containers, one for the app and another for the database and linking the two. Working with docker for the first time, there were quite a few gotchas involved so I wanted to share with you a comprehensive walkthrough.

You can find the demo app for this tutorial here: Github.com/Dockerize-Tutorial

This guide assumes you already have Docker installed, if not, install it now.

Step 1: Setup Postgres Docker Container

First, lets pull down and run the Official Postgres docker image from Docker Hub. We will give it the convenient name 'db' and also set the default 'postgres' user's password:

docker run --name db -e POSTGRES_PASSWORD=YOUR_PASSWORD -d postgres

The -d flag tells docker to run this container as a daemon so it will be running quietly in the background. Next, lets create our app's dedicated postgres user and create the database. To do this, let's open the containers bash shell:

docker exec -it db /bin/bash

From this bash shell lets launch psql:

psql -U postgres

Within the postgres interactive shell let's create the user and db:

CREATE USER app;
CREATE DATABASE testapp;
GRANT ALL PRIVILEGES ON DATABASE testapp TO app;

Then exit psql (ctrl-d). Now that we've set up the DB, we are going to edit a config file within this container so lets install a text editor (Vim! I choose you!).

apt-get update && apt-get install vim

Let's use our new superpowers to edit the postgres 'pg_hba.conf' configuration file which controls client authentication. We need to change this file to allow our custom postgres user we created above to access the DB running on this container from the app container we will be creating later. By default, only the 'postgres' user has this power. To discover the whereabouts of this config file we can ask postgres:

psql -U postgres

in the postgres interactive shell run:

SHOW hba_file;

Copy the file path returned and exit the psql shell (ctrl-d). Now lets edit this file with vim:

vi /var/lib/postgresql/data/pg_hba.conf

Change the last line in the file to the following and save the changes by typing :wq:

host all "app" 0.0.0.0/0 trust

Since this configuration file is only run when postgres boots up, we will now stop and start the db container. Exit back to your host computer (ctrl-d) and run:

docker stop db
docker start db

The changes should have taken effect and our Postgres container is now ready!

Step 2: Dockerize The Golang App

In order to Dockerize our Go App, we must create a Dockerfile within our projects' folder. If you don't want to Dockerize an existing Golang App you are currently working on, you can clone the Dockerize-tutorial Demo App and follow along with that application. In the root directory of our Golang project, create the Dockerfile:

touch Dockerfile

Within this file, we will add these three lines:

FROM golang:onbuild
RUN go get bitbucket.org/liamstask/goose/cmd/goose
EXPOSE 4000

The first line will run the 'onbuild' variant of the golang image that automatically copies the package source, fetches the application dependencies, builds the program, and configures it to run on startup. The second line will install 'goose', our migration tool within the app container while the last line will expose port 4000 (where our app will be running) of the container to the outside world. Once we have this, we can now build a docker image for our app. From within the project directory, run:

docker build -t app .

The result of this command is a docker image of our golang app called 'app'. We can now run a docker container from this image:

docker run -d -p 8080:4000 --name tutapp --link db:postgres app

Let's break this command down: -d tells docker to run this container as a daemon. -p 8080:4000 tells docker to map the containers port 4000 (where our app lives) to our host computer's port 8080. We conveniently name the docker container 'tutapp' for future fun and lastly, --link db:postgres links our app container to the postgres container we created earlier called 'db'.

The act of linking the two containers has given our app container access to an environment variable called '$POSTGRES_PORT_5432_TCP_ADDR'. This environment variable contains the host address we must use when connecting to the Postgres DB. For that reason, we must make sure that our dbconf.yml file sets the host to this variable:

db:
   driver: postgres
   open: host=$POSTGRES_PORT_5432_TCP_ADDR user=app dbname=testapp sslmode=disable

The config.go file in the demo app replaces this variable with the environment variable set within this container.

The last step required is to run the DB migrations for our app, so lets run 'goose up' inside the tutapp container:

docker exec -it tutapp goose up

To visit the app, go to http://localhost:8080 and you should see the app running!

(If your docker daemon is running on another machine (or in a virtual machine), you should replace localhost with the address of that machine. If you're using boot2docker on OS X or Windows you can find that address with the command 'boot2docker ip')

You now have a Golang app running and successfully communicating with a Postgres DB on a separate Docker Container. If anything was unclear or if anything didn't work for you, please leave me a comment below so that I can improve this post for others!