I recently switched a small Rust application to use Nix to build Docker images. The project is hosted on GitHub. Previously, the pipeline was simple: use docker/metadata-action to generate the labels and tags, feed those to docker/build-push-action to build and push the image. Nix added a new wrinkle, as the resultant Docker image would only have a single tag and no labels. I now had to apply those afterwards.

The Docker CLI provides a few shortcuts, so I didn’t need to write a new Dockerfile. The main issue was getting the --label arguments right. Given one (unquoted) label per line from metadata-action:

label1=with, commas,
label2=may contain "quotes"

What I wanted to run was this:

Bashdocker build - -t someimage:sometag --label "label1=with, commas," --label "label2=may contain \"quotes\""

Looping over the list directly wouldn’t work, because it would be substituted literally by Actions:

Bashdocker build - -t someimage:sometag --label label1=with, commas,\
--label label2=may contain "quotes"

Simply adding quotes would also be incorrect:

Bashdocker build - -t someimage:sometag --label "label1=with, commas,"\
--label "label2=may contain "quotes""

The command would succeed in this example with balanced quotes, since there are no syntactic errors from Bash’s perspective, but label2 would actually be given the value may contain quotes, due to Bash parsing. Other values might cause different problems.

The solution came from b-fuze on the DSI Discord:

Bashlabels="${{ steps.meta.outputs.labels }}"
label_args=$(IFS=$'\n'; printf ' --label %q' $labels)
echo "FROM $image_name" | docker build . -f - -t "$new_image_name" $label_args

What this does is:

  1. Store the list in a variable to get around other issues with quoting.

  2. Convert each key=value line in the list into --label key=value, with key=value correctly quoted for use as a single argument. This takes advantage of printf’s looping behaviour:

    The format is reused as necessary to consume all of the arguments.

  3. Pass all the arguments verbatim to docker build.