One of the most forgotten feature of the YAML files is the usage of templates in order to decrease the copy&paste effect on them. And yes, it’s a feature of YAML files, not from docker.
Repeated code is always considered a very bad practice, but configuration files & other scripts are always ignored on the already accepted as good practices in development circles.
Why?
On very dimple docker-compose.yml
files, usually with only one service, usually there is no need for this feature. For example:
version: '3.7'
services:
anthill:
image: ociotec/anthill:1.0.0
ports:
- '80'
networks:
- proxy_net
deploy:
placement:
constraints:
- node.labels.arch == x86
labels:
traefik.enable: 'true'
traefik.port: '80'
traefik.docker.network: 'proxy_net'
traefik.frontend.rule: 'Host: anthill.eht.ociotec.com'
networks:
proxy_net:
external: true
But when you have to define several services with same network definitions, maybe placement constrains, labels… YAML anchors & labels start to shine as a useful tool.
Now imagine that you want to add a second service really similar to the first one:
version: '3.7'
services:
anthill:
image: ociotec/anthill:1.0.0
ports:
- '80'
networks:
- proxy_net
deploy:
placement:
constraints:
- node.labels.arch == x86
labels:
traefik.enable: 'true'
traefik.port: '80'
traefik.docker.network: 'proxy_net'
traefik.frontend.rule: 'Host: anthill.eht.ociotec.com'
conway:
image: ociotec/conway:1.0.0
ports:
- '80'
networks:
- proxy_net
deploy:
placement:
constraints:
- node.labels.arch == x86
networks:
proxy_net:
external: true
On this example the only difference is the service name, image & one of the deployment labels, the rest is completely repeated.
The solution
A code snippet is the best description:
version: '3.7'
services:
anthill: &service
image: ociotec/anthill:1.0.0
ports:
- '80'
networks:
- proxy_net
deploy:
placement:
constraints:
- node.labels.arch == x86
labels: &labels
traefik.enable: 'true'
traefik.port: '80'
traefik.docker.network: 'proxy_net'
traefik.frontend.rule: 'Host: anthill.eht.ociotec.com'
conway:
<<: *service
image: ociotec/conway:1.0.0
deploy:
<<: *deploy
labels:
<<: *labels
traefik.frontend.rule: 'Host: conway.eht.ociotec.com'
networks:
proxy_net:
external: true
The magic:
- COPY: anchors are defined with
&
symbol after the element to copy; in the previous example there are 3 anchors:- one for the full service
anthill
, - one for the
deploy
section, - and other for the
deployment labels
.
- one for the full service
- PASTE: labels are defined with
<<: *
symbol, they should use the same name than the anchor; in the previous example there are 3 labels:- one for copying the full service definition into
conway
service, - one for the
deploy
section, - and other to copy the
deployment labels
.
- one for copying the full service definition into
The idea is that in the “repeated” service we only introduce what is different, take care than you could be tempted to omit the second and third anchors/labels but they are needed because we are redefining the the placement label traefik.frontend.rule
.
If they were omitted the YAML syntax interprets that you don’t need placement section and the first 3 labels, something like this:
...
conway:
image: ociotec/conway:1.0.0
ports:
- '80'
networks:
- proxy_net
deploy:
labels:
traefik.frontend.rule: 'Host: conway.eht.ociotec.com'
...
Conclusions
Once you start to define a lot of docker-compose.yml files you will find yourself repeating again and again the content of the file.
One day you realize you found & fixed one bug in one of the services but you missed other service in the same file.
By the way, the services used in this example are online, they are cellular automatons that I have developed myself, they are available here:
Updated with some extra interesting info to Docker compose references