Manage environmental differences with docker-compose overrides

Foreword: So recently I had the problem that I've deployed some software (Sentry) via docker-compose and needed to add some changes to the docker-compose.yml file to implement the correct routing for my setup. In doing so, I didn't want to fork the project to make my changes in the official docker-compose file and so I found the override "function" for docker-compose. And here is how you can use it.

Different Environments with differing scopes

While it is good practice to keep your environments as similar as possible, there can still be differences in their configuration. A few examples of this would be:

  1. As a software developer, you want to test your code on your local machine and you want to set up your application from source code, rather than from a final built container.
  2. You don't use the same database for your live and test systems.
  3. Your testing environment maybe use a different instance for example for your logging or your monitoring.

How docker-compose overrides work?

So docker-compose has an inbuilt function to merge two or more compose files together into a single merged file.

From the official docker-compose documentation:

By default, Compose reads two files, a docker-compose.yml and an optional docker-compose.override.yml file. By convention, the docker-compose.yml contains your base configuration. The override file, as its name implies, can contain configuration overrides for existing services or entirely new services.
If a service is defined in both files, Compose merges the configurations using the rules described in Adding and overriding configuration.

Through this feature, in my specific case, I was able to take the official docker-compose file and make my changes to docker-compose.override.yml. This spared me the creation of a repository fork and additionally, I get notified about direct changes in the official repository.

Example

To show the explained functionality a bit clearer I would like to show an example how the whole thing can look like. The example is based on the official Sentry docker-compose.yml and my docker-compose.override.yml to implement my traefik setup in front of the Sentry containers.

So here is a snippet of one of the services out of the official docker-compose.yml that I want to modify for my personal needs:

services:
  nginx:
    <<: *restart_policy
    ports:
      - "$SENTRY_BIND:80/tcp"
    image: "nginx:1.21.5-alpine"
    volumes:
      - type: bind
        read_only: true
        source: ./nginx
        target: /etc/nginx
    depends_on:
      - web
      - relay

And here is the content from my docker-compose.override.yml file:

version: '3'
networks:
  default:
  traefik:
    external:
      name: traefik_network
services:
  nginx:
    networks:
      - traefik
      - default
    labels:
      traefik.enable: true
      traefik.http.routers.sentry.rule: "Host(`example.com`)"
      traefik.docker.network: traefik_network
      traefik.http.routers.sentry.entrypoints: websecure
      traefik.http.routers.sentry.tls: true
      traefik.http.routers.sentry.tls.certresolver: default

As you can see, we add the traefik labels and some network configuration in our 'override' file.

After that, you can safe both compose files in the same location and just do your normal

docker-compose up -d

This is just a simple example. With the infinite flexibility of overrides, you can create any combination of environments to suit your needs.

Sources

Docker Compose documentation - https://docs.docker.com/compose/

Official Sentry repository - https://github.com/getsentry/self-hosted

Traefik documentation - https://doc.traefik.io/traefik/