When building a multiplatform image in GitHub Actions, sometimes you must use a custom configuration for the Docker daemon. For example, if you want to load multiplatform images to Docker, you must enable the containerd image store feature. And, of course, there is a GitHub action to do that:

    - name: Set up Docker
      uses: crazy-max/ghaction-setup-docker@v3
      with:
        daemon-config: |
          {
            "features": {
              "containerd-snapshotter": true
            }
          }

Then, if you need to pass the resulting image to another action running in a Docker container, you typically do something like this:

    - name: Build and push container image
      uses: docker/build-push-action@v6
      id: push
      with:
        context: ${{ inputs.context }}
        file: ${{ inputs.file }}
        platforms: ${{ inputs.platforms }}
        outputs: |
          type=docker,rewrite-timestamp=true
          type=image,push=${{ inputs.push }},rewrite-timestamp=true
        tags: |
          ${{ inputs.primaryTag }}
          ${{ inputs.tags }}
        build-args: ${{ inputs.args }}
        cache-from: ${{ inputs.cache-from }}
        cache-to: ${{ inputs.cache-to }}
        no-cache: ${{ inputs.no-cache }}
      env:
        SOURCE_DATE_EPOCH: 0

    - name: Security Scan
      shell: bash
      run: |
        docker run --rm \
          -v /var/run/docker.sock:/var/run/docker.sock \
          -v $(pwd)/.cache:/root/.cache \
          -v $(pwd):/workdir \
          -w /workdir \
          aquasec/trivy:0.57.1 image --format json --ignore-unfixed --pkg-types os --scanners vuln --db-repository ghcr.io/aquasecurity/trivy-db:2,public.ecr.aws/aquasecurity/trivy-db:2 ${{ inputs.primaryTag }}

The above steps build the image and then run the Aqua Trivy Vulnerability Scanner on the built image. inputs.push is true for pushes to the main branch and false for pull requests.

Suppose we have a custom image, and Dependabot wants to update its base image. Our custom image uses the same tag as the base image. If we use the above configuration, the workflow will fail: Trivy will be unable to find the image.

Trivy does not see the built image

But why? We see that docker image ls confirms that the image is there. But why doesn’t Trivy see it? The answer is simple: Docker contexts.

When we set up our custom Docker configuration with the containerd image store, the action created a new Docker context. If we run docker context ls, we will see something like this:

Docker contexts

We see that the current context is called setup-docker-action and it uses its own socket instead of the standard /var/run/docker.sock. And because we pass the wrong socket to Trivy, it talks to a different Docker instance that is unaware of the image we have just built. And because this is a pull request, we don’t push the image into the registry. Therefore, the images exists neither in the Docker image store, nor in the registry. If, however, we have passed .../nginx:latest to Trivy, it would download the old image from the registry, and we would get the wrong scan results.

My solution to this issue was to extract the socket from the current context and pass it to the scanner:

    - name: Get Docker socket
      id: socket
      run: echo docker_socket="$(docker context ls --format json | jq -r 'select(.Current == true) | .DockerEndpoint' | sed 's!^unix://!!')" >> "${GITHUB_OUTPUT}"
      shell: bash

    - name: Security Scan
      shell: bash
      run: |
        docker run --rm \
          -v ${{ steps.socket.outputs.docker_socket }}:/var/run/docker.sock \
# ...

When using custom Docker configurations in GitHub Actions, understanding the implications of Docker contexts is crucial to avoid workflow failures. Mismatched endpoints can lead to discrepancies between the runtime environment and external processes, resulting in errors or incorrect behavior. Never forget about Docker contexts.

Docker Context Issues in GitHub Actions
Tagged on:     

Leave a Reply

Your email address will not be published. Required fields are marked *