Docker Compose templating using YAML merge feature

Posted at (289 words, 2 minutes to read)
Tags:
Table of Contents

Useful trick when you have many of the same services in the docker-compose.yml file.

Imagine that we have some data processing application which has a master service and several different workers. All applications are written in the same language and use a common base docker container. In general this is a fairly common configuration. The docker-compose.yml file will look something like this:

version: "3.8"

services:
  master:
    build:
      context: .
    ports:
      - "8080:8080"
    volumes:
      - ".:/app"
    command: python run_master.py

  worker-foo:
    build:
      context: .
    depends_on:
      - master
    volumes:
      - ".:/app"
    environment:
      - MASTER_HOST=master:8080
    command: python run_foo_worker.py

  worker-bar:
    build:
      context: .
    depends_on:
      - master
    volumes:
      - ".:/app"
    environment:
      - MASTER_HOST=master:8080
    command: python run_bar_worker.py

It looks verbose. A lot of code duplication. Let’s fix it with YAML Anchors!

version: "3.8"

x-base: &base
  build:
    context: .
  volumes:
    - .:/app

x-worker: &worker
  <<: *base
  depends_on:
    - master
  environment:
    - MASTER_HOST=master:8080

services:
  master:
    <<: *base
    ports:
      - "8080:8080"
    command: python run_master.py

  worker-foo:
    <<: *worker
    command: python run_foo_worker.py

  worker-bar:
    <<: *worker
    command: python run_bar_worker.py

It looks much better! Such a file is easier to maintain, expand and edit in the future.

The x-field is a special property in the docker-compose.yml file. In the Docker compose documentation they are called extension fields. You can put any valid YAML code in them.

Merge problems

Note that YAML does merge at the top level and do not deep merge. So this code will not work as expected:

x-base: &base
  environment:
    - SUPER_SECRET=42

x-worker: &worker
  <<: *base
  environment:
    - MASTER_HOST=master:8080

The worker service will completely overwrite environment of base and will contain only MASTER_HOST. Unfortunately, this array combining cannot be achieved directly in the docker-compose file. Therefore, in such cases, you can use extends.

Solution for single variable

However, if you want to make only one value common, you can put that value into a variable, and use that variable as an array element.

x-super-secret: &super-secret SUPER_SECRET=42

x-base: &base
  environment:
    - *super-secret

x-worker: &worker
  <<: *base
  environment:
    - *super-secret
    - MASTER_HOST=master:8080

This can be useful for volumes, depends_on and other array-like directives.

Solution for single object

As for environment, you can put the shared variables in a separate object and combine them in the same way we did with services. 🙃

x-env: &env
  SUPER_SECRET: 42

x-base: &base
  environment:
    <<: *env

x-worker: &worker
  <<: *base
  environment:
    <<: *env
    MASTER_HOST: master:8080

That’s all for now. If this information was helpful to you, don’t forget to subscribe to receive notifications of new posts.