Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 117 additions & 38 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,66 @@ on:
workflow_call:
inputs:
publish:
description: 'Whether to sign and publish the image using cosign and GHCR'
description: "Whether to sign and publish the image using cosign and GHCR"
required: true
type: boolean
dockerfile:
description: 'Path to the Dockerfile to build'
description: "Path to the Dockerfile to build"
required: false
type: string
context:
description: 'Path to the build context'
description: "Path to the build context"
required: false
type: string
image_name:
description: 'Name of the image to build'
description: "Name of the image to build"
required: false
type: string
artifact_name:
description: "Name prefix for digest artifacts (defaults to 'digests'). Useful when running multiple docker builds in the same workflow."
required: false
type: string
default: "digests"

env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
IMAGE_NAME_LOWER: ""

jobs:
build:
runs-on: ubuntu-latest
# Build docker image for each architecture in parallel
build-arch:
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64

runs-on: ${{ matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: inputs.publish
uses: sigstore/cosign-installer@v3
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
echo "IMAGE_NAME_LOWER=$(echo '${{ inputs.image_name || env.IMAGE_NAME }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: arm64

# Workaround: https://github.com/docker/build-push-action/issues/461
- name: Setup Docker buildx
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: inputs.publish
uses: docker/login-action@v3
Expand All @@ -67,41 +72,115 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
images: ${{ env.REGISTRY }}/${{ inputs.image_name || env.IMAGE_NAME }}
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}

# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v5
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: ${{ inputs.context || '.' }}
push: ${{ inputs.publish }}
tags: ${{ steps.meta.outputs.tags }}
file: ${{ inputs.dockerfile || 'Dockerfile' }}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }},push-by-digest=true,name-canonical=true,push=${{ inputs.publish }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
file: ${{ inputs.dockerfile || 'Dockerfile' }}

# Sign the resulting Docker image digest except on PRs.
- name: Export digest
if: inputs.publish
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"

- name: Upload digest
if: inputs.publish
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact_name }}-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1

# Merge all architecture images into a single manifest
merge:
runs-on: ubuntu-latest
if: inputs.publish
needs:
- build-arch
permissions:
contents: read
packages: write
id-token: write

steps:
- name: Prepare
run: |
echo "IMAGE_NAME_LOWER=$(echo '${{ inputs.image_name || env.IMAGE_NAME }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV

- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: ${{ inputs.artifact_name }}-*
merge-multiple: true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}

- name: Create manifest list and push
id: manifest
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}@sha256:%s ' *)

- name: Inspect image
id: inspect
run: |
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}:${{ steps.meta.outputs.version }} --raw | jq -r '.manifests[0].digest'
digest=$(docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}:${{ steps.meta.outputs.version }} --raw | sha256sum | awk '{print "sha256:" $1}')
echo "digest=${digest}" >> $GITHUB_OUTPUT

# Install the cosign tool
# https://github.com/sigstore/cosign-installer
- name: Install cosign
uses: sigstore/cosign-installer@v3

# Sign the resulting Docker image digest.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: inputs.publish
env:
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
DIGEST: ${{ steps.inspect.outputs.digest }}
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
run: |
images=""
for tag in ${TAGS}; do
images+="${tag}@${DIGEST} "
done
echo "${images}" | xargs -n 1 cosign sign --yes