Simplify web application deployment with Docker Compose

Joseph Gefroh
4 min readSep 25, 2017

--

Modern web applications have a lot of moving parts. They have SPA front-ends, back-end application servers, databases, in-memory caches, search services, etc.

Docker has simplified the setup of these individual moving parts (read my intro guide on Docker), but managing the entire stack itself can be a complex endeavor.

Setting up even one instance of your web application can involve configuring and connecting all of these various components together.

It’s a pain and very error-prone!

Enter Docker Compose

Docker Compose lets you specify a set of instructions to run docker containers for portions of your web application.

The great thing about Docker Compose is that it will handle the launching and connecting of the various components in the order they need to be launched in, preventing the need to write incredibly complicated docker run commands.

A single docker-compose up command can launch an entire web application stack, coordinating connections between multiple docker containers.

.docker-compose.yml

It all starts with this. This is the configuration file you use to tell Docker Compose how your application is built. Let’s take a look at this .docker-compose.yml file for my personal project, Stockerize:

Stockerize is a Ruby on Rails application — it has a Ruby on Rails server component and a database component.

version: '2'
services:
web:
depends_on:
- db
build: './source/stockerize'
ports:
- "5000:8001"
command: >
bash -c "rm -f tmp/pids/server.pid &&
bundle exec rake db:migrate &&
foreman start"
env_file:
- web.env
db:
image: postgres
volumes:
- ./mounts/db_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: <PG_USER>
POSTGRES_PASSWORD: <PG_PASSWORD>
POSTGRES_DB: stockerize_prod

version

version: '2'

This line specifies what version of the configuration it is. As of this post, there are 3 versions available.

services

services:
web:
# All other stuff
db:
# All other stuff

This is where you list the individual components that this docker compose file will be in charge of starting and connecting. You can name them whatever you want.

In my config I’ve specified two services: web and db.

web

web is the alias I gave to the Ruby on Rails component of this stack. I could have just as easily called it rails or potato or app.

depends_on

depends_on:
- db

My Ruby on Rails web server needs to be able to access the database service.

This line tells docker-compose to start up the service I called db first and to make the db service available to the web service via hostname. By doing this, the web container can connect to the db container via the db hostname alias without knowing the IP address ahead of time.

build

build: './source/stockerize'

If you are building the image on the same server as docker-compose, you can specify the build context here. This is the directory that will be delivered to docker build when build is run.

You could alternatively use image and specify an image instead.

ports

ports:
- "5000:8001"

Services can expose ports to the host machine, allowing the host to access the services running on the container’s ports or route external connections to the container’s ports. The mapping doesn’t have to be a direct mapping either — you can change the port that is exposed.

The command above expose container port 5000, where Rails is running, as port 8001.

command

command: >
bash -c "rm -f tmp/pids/server.pid &&
bundle exec rake db:migrate &&
foreman start"

You can use this to specify the command to run in the container once it starts. I run a bash command that migrates the database and starts the Rails server, but it will be different for every service.

env_file

env_file:
- web.env

There’s certain fields you may want to keep environment-specific. The env_file lets you specify an external text file that contains these variables, and makes it available as environment variables in the container.

An .env file might look like below:

RAILS_ENV=production
DATABASE_URL=postgres://pguser:pgpassword@db:5432/db_name

db

My Rails web application needs a database to run, so this service definition specifies the database to use. Note that db is what I chose to call it — I could have called it database or data or potato.

image

image: postgres

Because I’m not actually building a database image, I’ll instead specify the pre-made image to use. It’ll be default look in the Docker Hub repository for the image named postgres, download, and pull it.

volumes

volumes:
- ./mounts/db_data:/var/lib/postgresql/data

Containers don’t keep their data between restarts, which isn’t very useful if you’re trying to persist data permanently for a database. volumes solves this problem by letting you map data volumes to the host that persist between resets.

The above configuration specifies to mount the host directory ./mounts/db_data to the container directory that stores the database data /var/lib/postgresql/data.

environment

environment:
POSTGRES_USER: <PG_USER>
POSTGRES_PASSWORD: <PG_PASSWORD>
POSTGRES_DB: stockerize_prod

Instead of specifying a .env file, you can also specify environment variables directly into the docker-compose.yml config. These three environment variables in particular are used by the postgres image to set up the database.

Running Docker Compose

Now that you’ve written your docker-compose.yml configuration, it’s time to run it!

You can use docker-compose up -d to start the services in the background.

If you need to stop the services, you can use docker-compose down.

If you need to rebuild the images, you can use docker-compose build.

Docker Compose will run through the configuration and link all the containers together, and then you’ll have yourself a running instance of your application! You can use NGINX to route external requests to the appropriate ports.

Did you find this story helpful? Please Clap to show your support!
If you didn’t find it helpful, please let me know why with a
Comment!

--

--

Joseph Gefroh

VP of Product and Engineering @ HealthSherpa. Opinions my own. Moved to Substack. https://jgefroh.substack.com/