Docker Compose in Production

Docker Compose facilitates the deployment of Dockerized applications. A series of Docker commands and arguments to start your application become a single command; docker-compose up. Such an invaluable tool makes deploying in both development and production environments a breeze. However, applications are generally run and configured much differently in production than in development. In this post, we will explore ways to separate your development and production configurations while keeping things DRY and maintaining the simplicity of Docker Compose commands.

Example Configuration

# docker-compose.yml

version: '3'

services:
  db:
    image: postgres:11.1
  api:
    build:
      context: .
      dockerfile: docker/api/Dockerfile
    depends_on:
      - db
    env_file:
      - dev.env
    ports:
      - "8000:8000"
    volumes:
      - .:/code

This is a basic Docker Compose configuration you might use in development for an app with a Postgres database. There are two configurations here that are specific to development that we will need to exclude from production.

First, env_file allows you to define environment variables in a file that will be set in the container. If your application doesn’t already use environment variables for sensitive information like database credentials, API keys, and debug settings, you will probably want to do that before you deploy your app to production. I like to commit the dev.env file to source control, so that starting the app in development is still as simple as docker-compose up, without having to first create the file. It should contain default development configurations.

The .:/code bind mount we have defined here is to allow code updates outside the container to automatically update the code inside the container, without rebuilding it. This is essential for development, but not desired for production. For production, it is better to bake the code into the image so that it can be reliably versioned.

Creating a Base Configuration

The first step to getting this configuration production-ready is to extract the development aspects of it. Docker Compose allows you to create a docker-compose.override.yml file that extends the base configuration in docker-compose.yml. This file is automatically read when running docker-compose up. This is where our development-specific settings will go.

# docker-compose.yml

version: '3'

services:
  db:
    image: postgres:11.1
  api:
    build:
      context: .
      dockerfile: docker/api/Dockerfile
    depends_on:
      - db
    ports:
      - "8000:8000"
# docker-compose.override.yml

version: '3'

services:
  api:
    env_file:
      - dev.env
    volumes:
      - .:/code

Creating the Production Configuration

Next, we need to extend the base configuration with production-specific settings. We will do this in a docker-compose.prod.yml file.

# docker-compose.prod.yml

version: '3'

services:
  api:
    command: gunicorn myapp.wsgi -b 0.0.0.0:8000
    env_file:
      - prod.env

For our simple example, we may want to run our application with a production-ready server rather than a development server. We can do this with the command setting, which will override CMD for a container.

As mentioned previously, we want our sensitive production settings to be extracted into environment variables. For this, we may want to read from a prod.env file. Unlike dev.env, this file should not be committed to source control.

Usage

Starting our application in development is still as simple as ever. docker-compose up will read the docker-compose.yml configuration and extend it with the docker-compose.override.yml configuration automatically.

Starting the app in production is slightly more verbose. I prefer it this way rather than vice-versa, as this command will be run with much less frequency than in development. docker-compose -f docker-compose.yml -f docker-compose.prod.yml up uses -f options to explicitly define which files should be read. This prevents Docker Compose from reading docker-compose.override.yml by default, and instead extends our base configuration with docker-compose.prod.yml, as we have specified.

Closing Thoughts

Using Docker Compose for both development and production is the path to effortless deployments without sacrificing security. By extending a base configuration with development and production-specific configurations, we can keep our files DRY.

To find out how to leverage Docker and Docker Compose for your application, see How to Dockerize Node.js and How to Dockerize Django and Postgres.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.