Methods to enable environments in Docker Compose

·

3 min read

In this blog post I'll show two methods how to enable different environments in Docker Compose and which of them I prefer most.

I use Docker Compose when I need to run a multi-container Docker application. Often I need to have different setups for different environments. For example, I want to set debug variable to false for a production environment and have an additional service for database administration for a development.

Docker Compose is a great tool, and it natively enables this in different ways. But usually we need to define common and environment-specific configurations and tell Compose how to merge them.

Code for this blog post is available here

Extending using multiple Compose files

With this method we just define environment-specific variables with the same service name across different configs.

If a service is defined in both files, Compose merges the configurations using the rules described in Adding and overriding configuration.

So if we have docker-compose.yml

version: "3.9"

services:

  db:
    image: postgres
    environment:
      - POSTGRES_PASSWORD

  app:
    image: python:3.9
    depends_on:
      - db
    ports:
      - 8080:8080

And docker-compose.dev.yml

services:

  db:
    ports:
      - 5432:5432

  app:
    environment:
      - DEBUG=true

  adminer:
    image: adminer

The merged services are as follows:

$ docker-compose -f docker-compose.yml -f docker-compose.dev.yml config

services:
  adminer:
    image: adminer
  app:
    depends_on:
      db:
        condition: service_started
    environment:
      DEBUG: "true"
    image: python:3.9
    ports:
    - published: 8080
      target: 8080
  db:
    environment:
      POSTGRES_PASSWORD: null
    image: postgres
    ports:
    - published: 5432
      target: 5432
version: '3.9'

Extending using the extends field

Docker Compose’s extends keyword enables the sharing of common configurations among different files, or even different projects entirely.

With this method, we explicitly define services we extend from. There'll be a base config with some common
things.

It's worth noting that this method is deprecated and work only for Compose file versions up to 2.1.

This is our common config common-services.yml

version: "2.1"

services:

  db:
    image: postgres
    environment:
      - POSTGRES_PASSWORD

  app:
    image: python:3.9
    depends_on:
      - db
    ports:
      - 8080:8080

And this is the environment-specific config docker-compose.dev.yml

services:

  db:
    extends:
      file: common-services.yml
      service: db
    ports:
      - 5432:5432

  app:
    extends:
      file: common-services.yml
      service: db
    environment:
      - DEBUG=true

  adminer:
    image: adminer

The merged services are as follows:

$ docker-compose -f docker-compose.dev.yml config

services:
  adminer:
    image: adminer
  app:
    environment:
      DEBUG: "true"
      POSTGRES_PASSWORD: null
    image: postgres
  db:
    environment:
      POSTGRES_PASSWORD: null
    image: postgres
    ports:
    - published: 5432
      target: 5432
version: '3.9'

$ docker-compose -f docker-compose.dev.yml config
services:
  adminer:
    image: adminer
  app:
    environment:
      DEBUG: "true"
      POSTGRES_PASSWORD: null
    image: postgres
  db:
    environment:
      POSTGRES_PASSWORD: null
    image: postgres
    ports:
    - published: 5432
      target: 5432
version: '3.9'

Conclusion

Personally, I prefer the first method because:

  • It is more transparent for you as to what parts your final config is constructed of as you define all files in a command;
  • It is not deprecated

But of course it requires more typing for a command :(