Table of Contents

Background

While working on my Docker image for webtrees, I ran into a peculiar issue. I had setup automated builds in Docker Hub for every tagged commit in my git repository. At first, I wanted to be able to have multiple tags for each image build, so that each build could be version tagged (like 1.7.15) and have the latest tag applied as well. Using an undocumented feature, I was able to get that to work. However, problems arose once I wanted to also properly label my image. Specifically, I was trying to add the BUILD_DATE and VCS_REF labels. With Docker Hub, this requires you to add your own build hook in order to pass in the needed build arguments. Using an example I found, I added my own build hook to my repository.

1#!/bin/bash
2
3docker build --build-arg VCS_REF=`git rev-parse — short HEAD` \
4  --build-arg BUILD_DATE=`date -u +”%Y-%m-%dT%H:%M:%SZ”` \
5  -t $IMAGE_NAME .

This is where everything fell apart. My image builds started failing!

Docker Hub build failure
Oh no

Issue

After some head scratching and debugging, I discovered the two-part problem.

Environment Variable

The first part of the problem was the IMAGE_NAME environment variable. Normally, this environment variable provides a value like index.docker.io/username/repo:tag. However, when you abuse Docker Hub’s tagging system to do multiple tags like I was doing, you instead get a value like index.docker.io/username/repo:tag1,tag2.

Build Hook

With the aforementioned malformed environment variable, the simple build hook script example I found completely falls flat, as this is not valid syntax.

Mysteriously, if you don’t use a custom build script, this does in fact work, so Docker must be able to handle this in their own scripts, with logic they don’t share.

Workaround

To fix this, you need to write your own build hook script that can handle multiple tags being passed to it. You can either tag the image during the build command (easier) or build the the image first, then docker tag it multiple times (a bit more difficult, since you have to reference the image properly). I was unable to find anyone else who has done this, so I wrote my own. This is what I have come up with and seems to work:

 1#!/bin/bash
 2
 3IFS=',' read -ra tags <<< "$DOCKER_TAG"
 4TAG_COMMAND=""
 5
 6for tag in "${tags[@]}"
 7do
 8    TAG_COMMAND="$TAG_COMMAND -t $DOCKER_REPO:$tag"
 9done
10
11docker build --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
12             --build-arg VCS_REF=`git rev-parse --short HEAD` \
13             $TAG_COMMAND .

This script takes in the DOCKER_TAG environment variable (which provides a comma-separated list of tags), splits it by comma, then builds the -t tagging argument for the build command. There is no harm in using this for an image with only a single tag, as this will still work perfectly fine.

Docker Hub build success
Success!

Conclusion

With a bit of abuse of some undocumented features, I was able to get my Docker image to build automatically on Docker Hub with all of the labels and tags I wanted.

Microbadger labels
Microbadger labels

This adventure though has really made me question my usage of Docker Hub’s image building. It seems like I could have avoided a lot of this hassle if I had set up builds with a different CI provider like GitHub Actions, and then just push to Docker Hub, instead of dealing with all of these weird, undocumented limitations. I also don’t know if or when this workaround will stop functioning.

References