How to use multiple image tags with docker-compose

Viewed 23921

According to this and this GitHub issues, currently there is no native way how to supply multiple tags for a service's image when using docker-compose to build one or multiple images.

My use case for this would be to build images defined in a docker-compose.yml file and tag them once with some customized tag (e.g. some build no. or date or similar) and once as latest.

While this can be easily achieved with plain docker using docker tag, docker-compose only allows to set one single tag in the image key. Using docker tag together with docker-compose is not an option for me since I want to keep all my docker-related definitions in the docker-compose.yml file and not copy them over into my build script.

What would be a decent work-around to achieve setting of multiple tags with docker-compose and without having to hardcode/copy the image names first?

5 Answers

I have some nice and clean solution using environment variables (bash syntax for default variable value, in my case it is latest but you can use anything ), this is my compose:

version: '3'
services:
  app:
    build: .
    image: myapp-name:${version:-latest}

build and push (if you need to push to the registry) with the default tag, change the version using environment variable and build and push again:

docker-compose build
docker-compose push
export version=0.0.1
docker-compose build
docker-compose push

I came up with a couple of work-arounds of different complexity. They all rely on the assumption that ${IMAGE_TAG} stores the customized tag that represents e.g. a build no. and we want to tag all services' images with this tag as well as with latest.

grep the image names from the docker-compose.yml file

images=$(cat docker-compose.yml | grep 'image: ' | cut -d':' -f 2 | tr -d '"')
for image in $images
do
  docker tag "${image}":"${IMAGE_TAG}" "${image}":latest
done

However, this is error prone if somebody adds a comment in docker-compose.yml which would e.g. look like # Purpose of this image: do something useful....

Build twice

Use ${IMAGE_TAG} as an environment variable in your docker-compose.yml file as described here in the first example.

Then, simply run the build process twice, each time substituting ${IMAGE_TAG} with a different value:

IMAGE_TAG="${IMAGE_TAG}" docker-compose build
IMAGE_TAG=latest docker-compose build

The second build process should be much faster than the first one since all image layers should still be cached from the first run.

Drawback of this approach is that it will flood your log output with two subsequent build processes for each single service which might make harder to search through it for something useful.

Besides, if you have any command in your Dockerfile which always flushes the build cache (e.g. an ADD command fetching from a remote location with auto-updating last-modified headers, adding files which are constantly updated by an external process etc.) then the extra build might slow things down significantly.

Parse image names from the docker-compose.yml file with some inline Python code

Using a real yaml parser in Python (or any other language such as Ruby or perl or whatever is installed on your system) is more robust than the first mentioned grep approach since it will not get confused by comments or strange but valid ways of writing the yml file.

In Python, this could look like this:

images=$(python3 <<-EOF # make sure below to indent with tabs, not spaces; or omit the "-" before "EOF" and use no indention at all
    import yaml
    content = yaml.load(open("docker-compose.build.yml"))
    services = content["services"].values()
    image_names = (service["image"].split(":")[0] for service in services)
    print("\n".join(image_names))
EOF
)

for image in ${images}
do
docker tag ${image}:${IMAGE_TAG} ${image}:latest
done

Drawback of this approach is that the machine executing the build has to have Python3 installed, along with the PyYAML library. As already mentioned, this pattern could similarly be used with Python2 or any other programming language that is installed.

Get image names with combination of some docker commands

The following approach using some native docker and docker-compose commands (using go-templates) is a bit more complex to write but also works nicely.

# this should be set to something unique in order to avoid conflicts with other running docker-compose projects
compose_project_name=myproject.tagging

# create containers for all services without starting them
docker-compose --project-name "${compose_project_name}" up --no-start

# get image names without tags for all started containers
images=$(docker-compose --project-name "${compose_project_name}" images -q | xargs docker inspect --format='{{ index .RepoTags 0}}' | cut -d':' -f1)

# iterate over images and re-tag
for image in ${images}
do
    docker tag "${image}":"${IMAGE_TAG}" "${image}":latest
done

# clean-up created containers again
docker-compose --project-name "${compose_project_name}" down

While this approach does not have any external dependencies and is more safe than the grep method, it might take a few more seconds to execute on large setups for creating and removing the containers (typically not an issues though).

As suggested by @JordanDeyton extends cannot by used anymore in Compose file format > 3 and the Extension fields capability added in the version 3.4 can replace it to achieve the same goal. Here is an example.

version: "3.4"
# Define common behavior
x-ubi-httpd:
  &default-ubi-httpd
  build: ubi-httpd
  # Other settings can also be shared
  image: ubi-httpd:latest

# Define one service by wanted tag
services:
  # Use the extension as is
  ubi-httpd_latest:
    *default-ubi-httpd
  # Override the image tag
  ubi-httpd_major:
    << : *default-ubi-httpd
    image: ubi-httpd:1
  ubi-httpd_minor:
    << : *default-ubi-httpd
    image: ubi-httpd:1.0
  # Using an environment variable defined in a .env file for e.g.
  ubi-httpd_patch:
    << : *default-ubi-httpd
    image: "ubi-httpd:${UBI_HTTPD_PATCH}"

Images can be built now with all the defined tags

$ docker-compose build
# ...

$ docker images | grep ubi-httpd
# ubi-httpd  1       8cc412411805  3 minutes ago  268MB
# ubi-httpd  1.0     8cc412411805  3 minutes ago  268MB
# ubi-httpd  1.0.1   8cc412411805  3 minutes ago  268MB
# ubi-httpd  latest  8cc412411805  3 minutes ago  268MB

There is now a built-in solution using buildx bake, released in v.0.7.0. This feature was implemented following to my suggestion in https://github.com/docker/buildx/issues/396.

Docker comes bundled with buildx installed, however, if you are on Mac and running Docker Desktop the bundled buildx version is older at the time of writing this and you will need to install the correct version of buildx in addition to Docker.

Add the x-bake extension field to your docker-compose.yaml:

version: '3.9'

services:
  my-app:
    image: my-repo/my-image:latest
    build:
      context: .
      dockerfile: Dockerfile
      x-bake:
        tags:
        - my-repo/my-image:${MY_TAG_1}
        - my-repo/my-image:${MY_TAG_2}
        - my-repo/my-image:${MY_TAG_3}

        - my-other-repo/my-image:${MY_TAG_1}
        - my-other-repo/my-image:${MY_TAG_2}
        - my-other-repo/my-image:${MY_TAG_3}

To build and tag the image run:

buildx bake --load

To build, tag and push to image to the repository or even to multiple repositories:

buildx bake --push
Related