dockermakefilegithub-actions

Different variables handling when running Make locally and in GitHub Actions


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.


Solution

  • 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.