How to use YAML anchors as a template for Docker Compose services

https://jvwilge.github.io/en/2024/05/03/docker-compose-yaml-anchors-composition.html

After getting the services with 'depends_on' cannot be extended error I started looking for a solution that also better reflects that we’re actually cloning a service and changing some parameters instead of a ‘is a’ relation.

I will demonstrate the solution using an OpenSearch 2 service since it has some snags you might also encounter in the real world, but you can apply this to most services. Our starting point is the Sample Docker Compose file by OpenSearch.

We will use that example as a base and add a second service. The most naïve way would be to copy/paste, but that’ll make quite a short article.

Using extends and depends_on simultaneously

To add a second service the easiest step is to ues the extends attribute and change some parameters in the extended service. Technically you’re not extending here, but in many cases you can get away with it. In our case we also used the depends_on attribute and after a recent (early 2024) Docker Compose upgrade it gave the depends_on cannot be extended error.

So why did you get services with 'depends_on' cannot be extended? The Docker Compose documentation explains the reasoning behind this not working. If you want more details search for the error and a lot of issues will pop up (with thorough explanations).

In GitHub issue 1988 I found quite some pieces to create to a proper solution.

The proper solution would something in the direction of an abstract/base service or a template. In the actual services section we create ‘instances’ of the base service and overwrite/add some parameters.

The difference between extends and an extension

One of the reasons it took me a while to find a fitting solution is that I thought extends and extension were the same thing.

The extends attribute is used to share configuration (even across files). An extension is used to modularize configuration.

Well that still sounds the same, doesn’t it? In the next paragraph things will become more clear. In my head I pretend that an extension is a template you can define as a top level element (which is normally not allowed by the YAML schema of Docker Compose) for later re-use .

Defining the service template as an extension

Defining a top level element is allowed though if you prefix it with x-. This element is ignored by Docker Compose, which is exactly what we want since we want to fill in the details later. As you might’ve guessed by now the x- stands for extension.

To use this template somwhere in the (same) YAML we can use what I call the programmatic copy/paste of YAML: YAML anchors. As always, be careful with the spaces here!

As a simple first step let’s only pull out the image name to an extension:

x-opensearch-service: &opensearch-service
  image: opensearchproject/opensearch:2.12.0
services:
  opensearch-node1:
    <<: *opensearch-service
    container_name: opensearch-node1
    environment:
      - cluster.name=opensearch-cluster
#... rest of file cut away for brevity ...#

Instead of running docker compose up and wait for errors I’d recommend using docker compose config, this will show the canonical compose.yaml. The fact that something works isn’t a guarantee that it is correct or has unwanted side effects.

As you can see the image is now inside services.opensearch-node1.

All right, the base is set up.

How to circumvent sequences for environment variables

The first snag you’ll probably hit is that you want to override environment variables (or at least add new ones besides the common ones). Unfortunately this is a YAML sequence, where merging is not allowed.

Luckily there is an escape hatch in this specific case, the env_file attribute. Here you can specify a file with environment variables. The values from (different) env_files are combined with environment to environment. It feels a bit like cheating, but on the other hand our compose.yaml becomes a bit more readable.

Create a file called compose-opensearch.env and add the following values:

discovery.type=single-node
bootstrap.memory_lock=true
OPENSEARCH_JAVA_OPTS="-Xms512m -Xmx512m"
DISABLE_INSTALL_DEMO_CONFIG=true
DISABLE_SECURITY_PLUGIN=true

Replace the earlier extension with:

x-opensearch-service: &opensearch-service
  image: opensearchproject/opensearch:2.12.0
  env_file: compose-opensearch.env

And you can remove the redundant environment values from the opensearch-node1 service.

Now check docker compose config and verify that the enviroment section of opensearch-node1 looks correct.

Summary

To summarize:

First create an extension that acts as a base template. Then create actual services where you override/add the attributes to the template using YAML anchors (except sequences!). docker compose config saves a lot of debugging time.

Here are some links that didn’t make it to the article but might still be helpful.

First published on May 3, 2024 at jvwilge.github.io

{
"by": "jvwilge",
"descendants": 0,
"id": 40245410,
"score": 2,
"time": 1714724484,
"title": "How to use YAML anchors as a template for Docker Compose services",
"type": "story",
"url": "https://jvwilge.github.io/en/2024/05/03/docker-compose-yaml-anchors-composition.html"
}
{
"author": "Jeroen van Wilgenburg",
"date": "2024-05-03T08:02:14.000Z",
"description": "How to use YAML anchors as a template for Docker Compose services - also fixes depends_on cannot be extended After getting the services with ‘depends_on’ cannot be extended error I started looking for a solution that also better reflects that we’re actually cloning a service and changing some parameters instead of a ‘is a’ relation.",
"image": null,
"logo": null,
"publisher": "Jeroen van Wilgenburg’s Blog",
"title": "How to use YAML anchors as a template for Docker Compose services - also fixes depends_on cannot be extended",
"url": "https://jvwilge.github.io/en/2024/05/03/docker-compose-yaml-anchors-composition.html"
}
{
"url": "https://jvwilge.github.io/en/2024/05/03/docker-compose-yaml-anchors-composition.html",
"title": "How to use YAML anchors as a template for Docker Compose services - also fixes depends_on cannot be extended",
"description": "How to use YAML anchors as a template for Docker Compose services - also fixes depends_on cannot be extended After getting the services with 'depends_on' cannot be extended error I started looking for a solution that also better reflects that we’re actually cloning a service and changing some parameters instead of a ‘is a’ relation.",
"links": [
"https://jvwilge.github.io/en/2024/05/03/docker-compose-yaml-anchors-composition.html"
],
"image": "",
"content": "<section>\n<p>After getting the <code>services with 'depends_on' cannot be extended</code> error I started looking for a solution that also better reflects that we’re actually cloning a service and changing some parameters instead of a ‘is a’ relation.</p>\n<p>I will demonstrate the solution using an OpenSearch 2 service since it has some snags you might also encounter in the real world, but you can apply this to most services. Our starting point is the <a target=\"_blank\" href=\"https://opensearch.org/docs/latest/install-and-configure/install-opensearch/docker/#sample-docker-compose-file-for-development\">Sample Docker Compose file by OpenSearch</a>.</p>\n<p>We will use that example as a base and add a second service. The most naïve way would be to copy/paste, but that’ll make quite a short article.</p>\n<h2 id=\"using-extends-and-depends_on-simultaneously\">Using extends and depends_on simultaneously</h2>\n<p>To add a second service the easiest step is to ues the <a target=\"_blank\" href=\"https://docs.docker.com/compose/multiple-compose-files/extends/\">extends attribute</a> and change some parameters in the extended service. Technically you’re not extending here, but in many cases you can get away with it. In our case we also used the <a target=\"_blank\" href=\"https://docs.docker.com/compose/compose-file/05-services/#depends_on\">depends_on attribute</a> and after a recent (early 2024) Docker Compose upgrade it gave the <code>depends_on cannot be extended</code> error.</p>\n<p>So why did you get <code>services with 'depends_on' cannot be extended</code>? The <a target=\"_blank\" href=\"https://docs.docker.com/compose/compose-file/05-services/#restrictions:~:text=%2C-,depends_on,-.\">Docker Compose documentation</a> explains the reasoning behind this not working. If you want more details search for the error and a lot of issues will pop up (with thorough explanations).</p>\n<p>In <a target=\"_blank\" href=\"https://github.com/docker/compose/issues/1988\">GitHub issue 1988</a> I found quite some pieces to create to a proper solution.</p>\n<p>The proper solution would something in the direction of an abstract/base service or a template. In the actual <code>services</code> section we create ‘instances’ of the base service and overwrite/add some parameters.</p>\n<h2 id=\"the-difference-between-extends-and-an-extension\">The difference between <code>extends</code> and an extension</h2>\n<p>One of the reasons it took me a while to find a fitting solution is that I thought <code>extends</code> and extension were the same thing.</p>\n<p><a target=\"_blank\" href=\"https://docs.docker.com/compose/compose-file/05-services/#extends\">The <code>extends</code> attribute</a> is used to share configuration (even across files).\n<a target=\"_blank\" href=\"https://docs.docker.com/compose/compose-file/11-extension/\">An extension</a> is used to modularize configuration.</p>\n<p>Well that still sounds the same, doesn’t it? In the next paragraph things will become more clear. In my head I pretend that an extension is a template you can define as a top level element (which is normally not allowed by the YAML schema of Docker Compose) for later re-use .</p>\n<h2 id=\"defining-the-service-template-as-an-extension\">Defining the service template as an extension</h2>\n<p>Defining a top level element is allowed though if you prefix it with <code>x-</code>. This element is ignored by Docker Compose, which is exactly what we want since we want to fill in the details later. As you might’ve guessed by now the <code>x-</code> stands for <a target=\"_blank\" href=\"https://docs.docker.com/compose/compose-file/11-extension/\">extension</a>.</p>\n<p>To use this template somwhere in the (same) YAML we can use what I call the programmatic copy/paste of YAML: <a target=\"_blank\" href=\"https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/\">YAML anchors</a>. As always, be careful with the spaces here!</p>\n<p>As a simple first step let’s only pull out the image name to an extension:</p>\n<div><pre><code><span>x-opensearch-service</span><span>:</span> <span>&amp;opensearch-service</span>\n <span>image</span><span>:</span> <span>opensearchproject/opensearch:2.12.0</span>\n<span>services</span><span>:</span>\n <span>opensearch-node1</span><span>:</span>\n <span>&lt;&lt;</span><span>:</span> <span>*opensearch-service</span>\n <span>container_name</span><span>:</span> <span>opensearch-node1</span>\n <span>environment</span><span>:</span>\n <span>-</span> <span>cluster.name=opensearch-cluster</span>\n<span>#... rest of file cut away for brevity ...#</span>\n</code></pre></div>\n<p>Instead of running <code>docker compose up</code> and wait for errors I’d recommend using <code>docker compose config</code>, this will show the <a target=\"_blank\" href=\"https://docs.docker.com/reference/cli/docker/compose/config/\">canonical compose.yaml</a>. The fact that something works isn’t a guarantee that it is correct or has unwanted side effects.</p>\n<p>As you can see the image is now inside <code>services.opensearch-node1</code>.</p>\n<p>All right, the base is set up.</p>\n<h2 id=\"how-to-circumvent-sequences-for-environment-variables\">How to circumvent sequences for environment variables</h2>\n<p>The first snag you’ll probably hit is that you want to override environment variables (or at least add new ones besides the common ones). Unfortunately this is a YAML sequence, <a target=\"_blank\" href=\"https://docs.docker.com/compose/compose-file/11-extension/#:~:text=can%27t%20be%20used%20with%20sequences.\">where merging is not allowed</a>.</p>\n<p>Luckily there is an escape hatch in this specific case, <a target=\"_blank\" href=\"https://docs.docker.com/compose/compose-file/05-services/#env_file\">the <code>env_file</code> attribute</a>. Here you can specify a file with environment variables. The values from (different) <code>env_file</code>s are combined with <code>environment</code> to <code>environment</code>. It feels a bit like cheating, but on the other hand our <code>compose.yaml</code> becomes a bit more readable.</p>\n<p>Create a file called <code>compose-opensearch.env</code> and add the following values:</p>\n<div><pre><code>discovery.type<span>=</span>single-node\nbootstrap.memory_lock<span>=</span><span>true\n</span><span>OPENSEARCH_JAVA_OPTS</span><span>=</span><span>\"-Xms512m -Xmx512m\"</span>\n<span>DISABLE_INSTALL_DEMO_CONFIG</span><span>=</span><span>true\n</span><span>DISABLE_SECURITY_PLUGIN</span><span>=</span><span>true</span>\n</code></pre></div>\n<p>Replace the earlier extension with:</p>\n<div><pre><code><span>x-opensearch-service</span><span>:</span> <span>&amp;opensearch-service</span>\n <span>image</span><span>:</span> <span>opensearchproject/opensearch:2.12.0</span>\n <span>env_file</span><span>:</span> <span>compose-opensearch.env</span>\n</code></pre></div>\n<p>And you can remove the redundant environment values from the <code>opensearch-node1</code> service.</p>\n<p>Now check <code>docker compose config</code> and verify that the enviroment section of <code>opensearch-node1</code> looks correct.</p>\n<h2 id=\"summary\">Summary</h2>\n<p>To summarize:</p>\n<p>First create an extension that acts as a base template. Then create actual services where you override/add the attributes to the template using YAML anchors (except sequences!). <code>docker compose config</code> saves a lot of debugging time.</p>\n<h2 id=\"links\">Links</h2>\n<p>Here are some links that didn’t make it to the article but might still be helpful.</p>\n<ul>\n <li><a target=\"_blank\" href=\"https://stackoverflow.com/questions/41063361/what-is-the-double-left-arrow-syntax-in-yaml-called-and-wheres-it-specifi\">stackoverflow - What is the « (double left arrow) syntax in YAML called, and where’s it specified?</a></li>\n <li><a target=\"_blank\" href=\"https://github.com/docker/compose/issues/3220\">Github Issue 3320 - services with ‘depends_on’ cannot be extended</a></li>\n <li><a target=\"_blank\" href=\"https://medium.com/@kinghuang/docker-compose-anchors-aliases-extensions-a1e4105d70bd\">Medium - Don’t Repeat Yourself with Anchors, Aliases and Extensions in Docker Compose Files</a></li>\n</ul>\n<blockquote>\n <p>First published on May 3, 2024 at <a target=\"_blank\" href=\"http://jvwilge.github.io/\">jvwilge.github.io</a></p>\n</blockquote>\n </section>",
"author": "Jeroen van Wilgenburg",
"favicon": "",
"source": "jvwilge.github.io",
"published": "2024-05-03T08:02:14+00:00",
"ttr": 159,
"type": "article"
}