Shared variables in docker-compose.yml
This post started out as me trying to solve a fairly straight forward problem (stated below), but instead fell into the rabbit hole of YAML structure and concepts.
Problem
I’ve got a docker-compose setup for running an app locally, it consists of one database service, and one api service.
It basically looks like this:
You can probably already guess the issue, I’m using the same literal password in 2 places, and I use the name of the database container as the database host for the api (also literals). For 2 services in a contrived example this might be alright, but to me, typing out the same value more than once spells trouble down the road, better to fix it now!
Solution
I want to be able to declare my 2 variables once, and use them twice.
Turns out that we’re able to accomplish this in multiple ways using concepts from both YAML and docker-compose.
- YAML Anchors and aliases: https://yaml.org/spec/1.2/spec.html#id2765878
- docker-compose extension fields (requires docker-compose.yml version 3.4+): https://docs.docker.com/compose/compose-file/#extension-fields
Let’s try a few scenarios to see how we may combine these concepts to solve different problems:
Reuse a scalar value, but rename the mapping (the problem stated at the beginning)
In this case I’d argue that the database
service “owns” the values for both the container name, and the password. This means that we may declare the values as anchors (identified by a &
character) at the same position that they are right now, and then output them as alias (identified by a *
character).
Our docker-compose.yml file would then look like this:
Which would result in the following environment variables on the api
service:
You might notice that I changed the syntax in both environment
mappings block from using the - {key}={value}
syntax, to use the {key}: {value}
syntax. That’s because the first one isn’t proper syntax, but works, atleast until we started to add the anchors and aliases, at which point it breaks.
Reuse an entire block/sequence
Lets say that we have an application where 2 clients communicate with the same backend, then we might want to have the same environment variables in both of these in order to communicate with the api.
In this case we might declare an anchor for the entire block:
Here the environment variables for both client1
and client2
will be exactly what’s in the &api-configuration
anchor.
This could be useful in some instances, but probably not in this one so let’s continue on.
Merge blocks/sequences spec
In the above example we’re limited to having client1
and client2
’s environment variables to be exactly the same as the api
’s. But what if client1
and client2
have other variables that the api
doesn’t have? And what if these variables aren’t common between client1
and client2
? Let’s instead merge the api
environment
variables into each respective client.
Here we use the <<
merge type to merge the &api-configuration
into each respective clients environment
variables. If you want to read up on the exact merge rules then check out the Specification
The output from this would be:
Extension fields spec
Building on the above example, let’s say that our api
doesn’t actually know its own hostname (at least not through this configuration), how do we avoid getting that variable into its environment
variables?
In this case I’d break common blocks into extension fields (docker-compose specific, requires version 3.4+ of docker-compose.yml):
We’ve now defined the x-common-extensions
(with the anchor name &common
), and the x-client-common-extensions
(with the anchor name &client-common
) extensions. The name of the extensions themselves aren’t important (not used by us), except that they have to start with x-
. Using this you’re able to create a hierarchy of commom blocks. If you favor single level composition over the hierarchy, then you may achieve the same end result with the following:
The difference here is that we’ve moved the merge from &common
into &client-common
, and put multiple merges into the clients environment blocks instead.
The end result will be the same, namely:
Scalar value as a extension field
Let’s say that we want to have the hostname
in the api
environment
variables anyway, but we want it to have a different key, how could we do that?
One way would be to define that value as an extension field on it’s own, not as part of a block, something like this:
With the end result:
Conclusion
Finding out how to do this wasn’t too straight forward, it did take a bit of digging, especially finding the concept of anchors/alias, hopefully this might help someone trying to accomplish something similar.
This post has focused on the environment
block. But anchors/alias are possible to use in any place in any yaml definition (atleast ones that are parsed by a 1.2+ parser), and docker-compose extension fields are possible to use anywhere in your service definitions.