GoDoRP: docker-compose for Development and Production

Josh McMenemy
Feb 25, 2017 · 5 min read

GoDoRP (GoLang, Docker, React, PostgreSQL) is a simple CRUD web app where a user can add messages to a message board. This article will show you how to use docker-compose to run the app locally in dev mode and build production images. Although the example applies specifically to a GoLang, React, Postgres stack, the ideas are generally applicable.

Why use Docker for Development:

  • Quick on-boarding (anyone with Docker installed can start coding with one terminal command without having to install technologies, connect services, or set up environmental variables). Especially useful for open source projects.
  • Dev environment is the same as production
  • More reasons stated in this medium article

What you will learn:

  • How to set-up a docker-compose.yaml file for a GoDoRP stack
  • How to write a single Dockerfile for React that allows you to build a dev container that will reload code changes you make locally (without rebuilding the container) and a production container that serves the static files
  • How to write a single Dockerfile for GoLang that allows you to build a dev container that will reload code changes you make locally (without rebuilding the container) and a production container that serves the binary build
  • How to make a PostgreSQL container that will persist data through container re-builds
  • How to switch between building production and development containers with one command

How to do it:

Note: This repo contains the code for the app and all the below Dockerfiles.

First lets start with the docker-compose.yaml file.

Running ‘$ docker-compose up’ sets up the React front end, Go backend api, and Postgres db for development from the Dockerfiles in the project. As stated earlier dev mode allows hot reloading of code changes on your local machine without re-building the container. If you want to see this in action follow the directions in the repo’s README.

The hot re-loading is done by sharing files on the local machine with the Docker container through volumes. This is different than the ADD or COPY commands which put a copy of the files in the Docker container. The app_env build arg is also important for setting up dev and production differences within the Dockerfiles.

Lets start with the GoLang Dockerfile.

The main things to note are:

  • The app_env arg will set APP_ENV to it’s value (dev or production). This is important because the APP_ENV variable will remain in the container after it is built, which allows for setting additional dev/production settings within the code. However, passing just the APP_ENV to this Dockerfile through compose would not work because passed ENV variables are not available at build time.
  • The CMD will use the set APP_ENV to serve the binary if it is set as production. If it is not production, pilu/fresh is used to watch the code for changes and allow code reloading without re-building the container.
  • The database package is in a vendor folder so that the github path does not need hardcoded. This makes the codebase more portable for starting other projects. The database package is needed to connect the GO API to the Postgres database and is discussed below.
  • This Dockerfile is discussed in more detail in this medium article.

Code in database package to init DB connection:

func Init() (*gorm.DB, error) {
// set up DB connection
// then attempt to connect 10 times over 10 seconds
connectionParams := "user=docker password=docker sslmode=disable host=db"
for i := 0; i < 10; i++ {
DB, err = gorm.Open("postgres", connectionParams)
if err == nil {
break
}
time.Sleep(1 * time.Second)
}

if err != nil {
return DB, err
}
// create table if it does not exist
if !DB.HasTable(&Post{}) {
DB.CreateTable(&Post{})
}

return DB, err
}

The trying to connect over ten seconds is needed because the database needs a couple seconds to set up when running docker-compose before it can accept connections. The full database init.go file is here. Also, gorm.Open tries to ping the database upon connecting. If you use just a base SQL driver you will have to run the Ping command separately to make sure a connection has been established.

Now onto the front end React Dockerfile.

The main things to note are:

  • The app_env arg will set NODE_ENV to it’s value (dev or production). This is important because the NODE_ENV variable will remain in the container after it is built, which allows for setting additional dev/production settings within the code. However, passing just the APP_ENV to this Dockerfile through compose would not work because passed ENV variables are not available at build time.
  • The CMD will use the set APP_ENV to serve the optimized static files if production. Otherwise it will watch the code and hot reload on changes (similar to webpack — watch). Both the optimized static files and hot reloading are done using Facebook’s create-react tool.
  • This Dockerfile and Facebook’s create-react tool are further discussed in this medium article.

The postgres Dockerfile does not have any customizations. One gotcha (which cost me several painful hours) to watch out for is that the default init script that the Postgres Docker image usually looks for will not run if you are sharing a volume. Sharing a data volume is needed if you want data to persist through container rebuilds. If the shared file does not exist locally it will create the shared file.

Congrats! That is all the logic behind setting up a Docker GoDoRP dev environment using docker-compose up!

To run the code locally in production mode run the follow command:

$ export APP_ENV=production
$ docker-compose up --build

Then to switch back to dev just change the APP_ENV shell variable back to dev.

$ export APP_ENV=dev
$ docker-compose up --build

To build the production images for Dockerhub run the following terminal commands:

$ docker build ./api --build-arg app_env=production 
$ docker build ./frontend --build-arg app_env=production
$ docker build ./db

That’s it! Now anyone with Docker can run your app locally in dev mode and start contributing with one docker-compose command! And, you can also use the same Dockerfiles to build your production images. If you have any suggestions to improve this setup, please respond below (preferably in a polite manner). If you found the code helpful, please star the repo.

If you are interested in using continuous integration to auto deploy these production images, then keep an eye out for my next post.

 by the author.

Josh McMenemy

Written by

Engineer at Synthego

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade