how-to: reuse the docker-compose file for different environments

I recently stumbled upon docker-compose's ability to share configurations between .yml files. This means that you can reuse the docker-compose.yml used for development and testing for different environments by providing another .yml to override settings which differ. Nice! Let's look at an example:

Let's assume we have the following service, which in our case launches a ghost blog container.

# docker-compose.yml

version: '3.1'

services:
  ghost:
    container_name: my-ghost-blog
    image: ghost:2.6.2-alpine
    volumes:
    - ./some/path/:/var/lib/ghost/content/
    ports:
    - 52368:2368
    environment:
      url: http://localhost:52368

For development this is fine and we can use docker-compose up to happily verify that our ghost blog is working as intended. But for production we have to change a few things:

  1. The volume will be in another directory on the production machine
  2. The port will have to be 80 not 52368
  3. The url will be different.

Instead of changing the values inside docker-compose.yml for deployment, which is error-prone has to be done manually every time, we instead use docker-compose's ability to share and overwrite settings. To do that, first we change the docker-compose.yml into a "base" compose file, which only contains values that are either identical in all environments or can be overwritten later by another .yml:

# docker-compose.yml

version: '3.1'

services:
  ghost:
    container_name: my-ghost-blog
    image: ghost:2.6.2-alpine
    volumes:
    - /some/path/:/var/lib/ghost/content/
    environment:
      url: http://localhost:52368

Additionally, we add another file called docker-compose.override.yml which only contains the now missing settings for our development environment:

# docker-compose.override.yml

version: '3.1'

services:
  ghost:
    ports:
    - 52368:2368

At this point running docker-compose up will take both the docker-compose file and the docker-compose.override file, merge them and run the resulting service. To see which configuration is used in particular, run docker-compose config. Note how all settings from docker-compose.yml are still there and the port inside docker-compose.override.yml was added:

# "docker-compose config" output

services:
  ghost:
    container_name: my-ghost-blog
    environment:
      url: http://localhost:52368
    image: ghost:2.6.2-alpine
    ports:
    - 52368:2368/tcp
    volumes:
    - /some/path:/var/lib/ghost/content/:rw
version: '3.1'

But how does this help us for production? Easy: we create yet another .yml called docker-compose.prod.yml, which specifies the environment specific configurations for production we listed earlier:

# docker-compose.prod.yml
# NOTE: keep this file secret and DON'T add it to version control

version: '3.1'

services:
  ghost:
    ports:
    - 80:2368
    volumes:
    - /some/prod/path/:/var/lib/ghost/content/
    environment:
      url: http://example-blog.io

To let docker-compose know to use this file instead of docker-compose.overrride.yml to overwrite the base file, we use a different command:

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up

To see the complete configuration before running it, we can use config instead of up again. Note how the url and the volume settings were overwritten and the bound port is now 80:

# "docker-compose -f docker-compose.yml -f docker-compose.prod.yml config" output

services:
  ghost:
    container_name: my-ghost-blog
    environment:
      url: http://example-blog.io
    image: ghost:2.6.2-alpine
    ports:
    - 80:2368/tcp
    volumes:
    - /some/prod/path:/var/lib/ghost/content/:rw
version: '3.1'

Read the chapter Adding and overriding configuration to better understand how particular configurations are merged and overwritten.

So now we can use both docker-compose.yml and docker-compose.override.yml for development and testing, while for production (or any other environment) we can define a docker-compose.prod.yml and reuse our docker-compose.yml file. Cool!

TL:DR

  1. Reduce docker-compose.yml to contain base configurations only, which are shared among all environments.
  2. Add docker-compose.override.yml which contains all missing configurations for development. docker-compose up now overwrites and merges both files.
  3. For deployment to production keep a secret docker-compose.prod.yml, which contains production configurations. Use docker-compose -f docker-compose.yml -f docker-compose.prod.yml up to run your service based on the production settings.
  4. Use config instead of up to see and verify the merged compose configuration before running it.

Show Comments