Is it possible to define a volume once for multiple services?

We’re using Docker and Docker Compose for our local development environment, but we have tons of network storages we need to attach to our application, at least 3 storages for each client, and can go up to 10. I think they made this for billing the clients a lot easier.

But this made things really a lot tedious and there are lot of clutters in the definition of services. Is it possible to define all the shared volumes once and all the services that I want to attach these volumes will be marked with a single label or something? I’m expecting them to go to the same path in the containers.

For example:

services:
  web-api:
    #all the other service definitions here
  volumes:
    - tempfiles:/app/shared/tempfiles
    - smb-fonts:/app/resources/fonts
    - smb-client1-templates-legal:/app/shared/templates/client1/legal
    - smb-client1-templates-invoice:/app/shared/templates/client1/invoice
    - smb-client1-templates-others:/app/shared/templates/client1/others
    - smb-client1-output-legal:/app/shared/output/client1/legal
    - smb-client1-output-invoice:/app/shared/output/client1/invoice
    - smb-client1-output-others:/app/shared/output/client1/others
    - smb-client2-templates-legal:/app/shared/templates/client2/legal
    - smb-client2-templates-marketing:/app/shared/templates/client2/marketing
    - smb-client2-output-legal:/app/shared/output/client2/legal
    - smb-client2-output-marketing:/app/shared/output/client2/marketing

  backgroundworker:
    #all the other service definitions here
  volumes:
    - smb-fonts:/app/resources/fonts
    - smb-client1-templates-legal:/app/shared/templates/client1/legal
    - smb-client1-templates-invoice:/app/shared/templates/client1/invoice
    - smb-client1-templates-others:/app/shared/templates/client1/others
    - smb-client1-output-legal:/app/shared/output/client1/legal
    - smb-client1-output-invoice:/app/shared/output/client1/invoice
    - smb-client1-output-others:/app/shared/output/client1/others
    - smb-client2-templates-legal:/app/shared/templates/client2/legal
    - smb-client2-templates-marketing:/app/shared/templates/client2/marketing
    - smb-client2-output-legal:/app/shared/output/client2/legal
    - smb-client2-output-marketing:/app/shared/output/client2/marketing

volumes:
  tempfiles:
  smb-fonts:
    external: true
  smb-client1-templates-legal:
    external: true
  smb-client1-templates-invoice:
    external: true
  smb-client1-templates-others:
    external: true
  smb-client1-output-legal:
    external: true
  smb-client1-output-invoice:
    external: true
  smb-client1-output-others:
    external: true
  smb-client2-templates-legal:
    external: true
  smb-client2-templates-marketing:
    external: true
  smb-client2-output-legal:
    external: true
  smb-client2-output-marketing:
    external: true

As you can see, all the volumes defined from smb-fonts in the top-level volumes are in both services. With only 2 clients, the two services are already cluttered with definitions of volumes that goes in the same path. I can probably move the top-level volumes definition to an override yaml file, but I think I still need to define these volumes for each service. Any idea to achieve this?

Generally you could leverage yaml anchors to define a reusable fragment, and use its alias to inject the content of the fragment: https://docs.docker.com/compose/compose-file/10-fragments/.

This works like a charm for map style definitions. Though, I doubt aliases will work on list values, like used in volumes.

1 Like

I tried that but yeah, it didn’t worked. It would be neat if you can merge any anchor into any service.

x-shared-volumes: &shared-volumes
  volumes:
    - smb-fonts:/app/resources/fonts
    - smb-client1-templates-legal:/app/shared/templates/client1/legal
    - smb-client1-templates-invoice:/app/shared/templates/client1/invoice
    - smb-client1-templates-others:/app/shared/templates/client1/others
    - smb-client1-output-legal:/app/shared/output/client1/legal
    - smb-client1-output-invoice:/app/shared/output/client1/invoice
    - smb-client1-output-others:/app/shared/output/client1/others
    - smb-client2-templates-legal:/app/shared/templates/client2/legal
    - smb-client2-templates-marketing:/app/shared/templates/client2/marketing
    - smb-client2-output-legal:/app/shared/output/client2/legal
    - smb-client2-output-marketing:/app/shared/output/client2/marketing

services:
  web-api: <<: *shared-volumes
    #all the other service definitions here
    volumes:
      - tempfiles:/app/shared/tempfiles

it will become like this

services:
  web-api:
    #all the other service definitions here
    volumes:
      - tempfiles:/app/shared/tempfiles
      - smb-fonts:/app/resources/fonts
      - smb-client1-templates-legal:/app/shared/templates/client1/legal
      - smb-client1-templates-invoice:/app/shared/templates/client1/invoice
      - smb-client1-templates-others:/app/shared/templates/client1/others
      - smb-client1-output-legal:/app/shared/output/client1/legal
      - smb-client1-output-invoice:/app/shared/output/client1/invoice
      - smb-client1-output-others:/app/shared/output/client1/others
      - smb-client2-templates-legal:/app/shared/templates/client2/legal
      - smb-client2-templates-marketing:/app/shared/templates/client2/marketing
      - smb-client2-output-legal:/app/shared/output/client2/legal
      - smb-client2-output-marketing:/app/shared/output/client2/marketing

It is a yaml syntax feature, not Docker. I also read in an old issue that yaml loaders in v1.3 will be encouraged not to support it by default. Docker Compose is not a template system, it is just nice that it still uses a yaml loader that supports these yaml features. If you want more complex configurations reusing some parameters, you will need to use a template system like Jinja. Which is also used in Ansible where you can generate compose files or even use it instead of a compose file.

Merging key-value pairs in yaml is also useful to have default, common values and override a key in a specific compose service. It is not possible in a list so I guess this could be the reason why it was not implemented in yaml.