I have the following GitHub workflow:
name: Makefile test
on:
push:
env:
MY_SECRET_NAME: ${{ secrets.MY_SECRET_NAME }}
jobs:
test-job:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Test makefile
run: make build
This is the makefile that is executed:
.PHONY: build
build:
rm -f my_secret.txt
docker build --no-cache --progress plain --debug -t ci:latest -f Dockerfile --secret id=MY_SECRET_NAME .
$(eval IMAGE_ID=$(shell docker create ci:latest))
docker cp "${IMAGE_ID}:/tmp/my_secret.txt" my_secret.txt
cat my_secret.txt
This is the Dockerfile:
FROM alpine:latest
WORKDIR /tmp
RUN --mount=type=secret,id=MY_SECRET_NAME \
echo $(cat /run/secrets/MY_SECRET_NAME) > /tmp/my_secret.txt
When running MY_SECRET_NAME=123 make build
locally, I get the correct result: the file my_secret.txt
contains the expected value (I run it on Ubuntu and trigger it from zsh).
When I run the above workflow on GitHub Actions, I get the following error:
docker cp ":/tmp/my_secret.txt" my_secret.txt
must specify at least one container source
make: *** [Makefile:8: build] Error 1
My question: Why is the variable IMAGE_ID
not set on GitHub Actions, but does it work just fine locally? My impression was that make
uses sh
unless told otherwise.
This is wrong:
build:
rm -f my_secret.txt
docker build --no-cache --progress plain --debug -t ci:latest -f Dockerfile --secret id=MY_SECRET_NAME .
$(eval IMAGE_ID=$(shell docker create ci:latest))
docker cp "${IMAGE_ID}:/tmp/my_secret.txt" my_secret.txt
cat my_secret.txt
Here's a tip for writing makefiles: it is always wrong to use $(eval ...)
and $(shell ...)
inside a recipe (1)
A makefile recipe is ALREADY a shell and so you should not need shell
. And eval
is used to modify make's internal understanding of the makefile which you should never do in a recipe.
When make is going to run a recipe it first expands the entire recipe, including all the lines. In this case, it means your shell
and eval
are invoked before any of the commands, including the docker build
command, are executed.
You should always use shell operations when writing recipes, like this:
build:
rm -f my_secret.txt
docker build --no-cache --progress plain --debug -t ci:latest -f Dockerfile --secret id=MY_SECRET_NAME .
docker cp "$$(docker create ci:latest):/tmp/my_secret.txt" my_secret.txt
cat my_secret.txt
(1) There are actually very rare, very obscure, very complex situations where you might want to use shell
or eval
in a recipe, but unless you're a real make
maven and have an exceedingly complicated build environment you should never do it.