headroom/.github/workflows/docker.yml
chopratejas f3d5392cc8 ci(docker): fix Argument list too long when signing bake outputs
The cosign signing step passed bake metadata via env var:

  env:
    BAKE_META: ${{ steps.bake.outputs.metadata }}
  run: echo "$BAKE_META" | jq ...

For large bake targets (code-nonroot, runtime-code-nonroot) the
metadata JSON is large enough that combined argv+env at bash spawn
exceeds Linux ARG_MAX (~128 KiB on ubuntu-latest), so bash dies with
E2BIG before the script even runs.

Switch to writing metadata into a heredoc-backed temp file, then read
it via jq file input. Heredocs put the JSON in the script body itself,
which bash reads from a temp file (no ARG_MAX limit), bypassing the
env-size ceiling entirely.

Module: .github/workflows/docker.yml
2026-04-27 12:59:03 -07:00

220 lines
9.3 KiB
YAML

name: Docker
on:
workflow_call:
inputs:
version:
description: "Version to stamp into the image contents and exact image tag"
required: false
type: string
enable_ref_tags:
description: "Whether to emit branch/PR ref tags"
required: false
default: true
type: boolean
workflow_dispatch:
inputs:
version:
description: "Version to stamp into the image contents and exact image tag"
required: false
release:
types: [published]
env:
REGISTRY: ghcr.io
permissions:
contents: read
packages: write
id-token: write # For cosign keyless signing via Sigstore OIDC
jobs:
docker-variant-tags:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- variant: ""
bake_target: runtime
- variant: nonroot
bake_target: runtime-nonroot
- variant: code
bake_target: runtime-code
- variant: code-nonroot
bake_target: runtime-code-nonroot
- variant: slim
bake_target: runtime-slim
- variant: slim-nonroot
bake_target: runtime-slim-nonroot
- variant: code-slim
bake_target: runtime-code-slim
- variant: code-slim-nonroot
bake_target: runtime-code-slim-nonroot
steps:
- uses: actions/checkout@v6
- name: Normalize image name
id: image-name
run: |
image_name="$(printf '%s' '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')"
printf 'image_name=%s\n' "$image_name" >> "$GITHUB_OUTPUT"
- name: Determine image version
id: version
env:
MANUAL_VERSION: ${{ inputs.version || github.event.inputs.version }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
version="${MANUAL_VERSION#v}"
if [ -z "$version" ] && [ -n "$RELEASE_TAG" ]; then
version="${RELEASE_TAG#v}"
fi
printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT"
- name: Set up Python
if: steps.version.outputs.version != ''
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Sync versioned files for image build
if: steps.version.outputs.version != ''
run: |
python scripts/version-sync.py --version ${{ steps.version.outputs.version }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute short SHA
id: short-sha
run: printf 'sha=%s\n' "${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"
- name: Extract metadata (variant)
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ steps.image-name.outputs.image_name }}
tags: |
type=ref,event=branch,enable=${{ inputs.enable_ref_tags != 'false' && github.event_name != 'release' }},suffix=${{ matrix.variant != '' && format('-{0}', matrix.variant) || '' }}
type=ref,event=pr,enable=${{ inputs.enable_ref_tags != 'false' && github.event_name != 'release' }},suffix=${{ matrix.variant != '' && format('-{0}', matrix.variant) || '' }}
type=raw,value=${{ steps.version.outputs.version }},enable=${{ steps.version.outputs.version != '' }},suffix=${{ matrix.variant != '' && format('-{0}', matrix.variant) || '' }}
type=raw,value=${{ steps.version.outputs.version }}-${{ steps.short-sha.outputs.sha }},enable=${{ steps.version.outputs.version != '' && matrix.variant == '' }}
type=raw,value=${{ steps.version.outputs.version }}-${{ matrix.variant }}-${{ steps.short-sha.outputs.sha }},enable=${{ steps.version.outputs.version != '' && matrix.variant != '' }}
type=semver,pattern={{version}},suffix=${{ matrix.variant != '' && format('-{0}', matrix.variant) || '' }}
type=semver,pattern={{major}}.{{minor}},suffix=${{ matrix.variant != '' && format('-{0}', matrix.variant) || '' }}
type=semver,pattern={{major}},suffix=${{ matrix.variant != '' && format('-{0}', matrix.variant) || '' }}
type=sha,format=short,prefix=${{ matrix.variant != '' && format('{0}-', matrix.variant) || 'sha-' }}
type=raw,value=${{ matrix.variant }},enable=${{ matrix.variant != '' }}
- name: Build and push variant (bake)
id: bake
uses: docker/bake-action@v7
with:
files: |
./docker-bake.hcl
cwd://${{ steps.meta.outputs.bake-file-tags }}
cwd://${{ steps.meta.outputs.bake-file-labels }}
targets: ${{ matrix.bake_target }}
push: true
set: |
*.cache-from=type=gha
*.cache-to=type=gha,mode=max
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Sign images with cosign (keyless via Sigstore OIDC)
env:
# Route signatures to a sibling GHCR package so the main image's
# package version list stays clean. GHCR does not implement the OCI 1.1
# referrers API yet (community discussion #163029, June 2025), so
# cosign's OCI 1.1 mode falls back to writing referrer tags into the
# *image's* repo. Using legacy signature mode here lets COSIGN_REPOSITORY
# actually relocate the artifacts. Verifiers must export the same
# COSIGN_REPOSITORY value when running 'cosign verify'.
COSIGN_REPOSITORY: ${{ env.REGISTRY }}/${{ steps.image-name.outputs.image_name }}-signatures
run: |
# Write bake metadata to a file rather than pass it through
# the `env:` block. The `code-nonroot` and `runtime-code-nonroot`
# bake targets emit metadata blobs large enough that combined
# argv+env at bash spawn exceeds Linux ARG_MAX (~128 KiB on
# ubuntu-latest), failing with `E2BIG: Argument list too long`
# before the script can run. The heredoc puts the JSON in the
# script body (which is read from a temp file by bash, no
# ARG_MAX limit) and we read it back via `jq -f`-style file
# input. Sentinel chosen long enough that no JSON payload is
# plausibly going to collide with it.
cat > "${RUNNER_TEMP}/bake_meta.json" <<'__HEADROOM_BAKE_META_EOF__'
${{ steps.bake.outputs.metadata }}
__HEADROOM_BAKE_META_EOF__
jq -r 'to_entries[].value."containerimage.digest" // empty' \
"${RUNNER_TEMP}/bake_meta.json" | while read -r digest; do
image="${{ env.REGISTRY }}/${{ steps.image-name.outputs.image_name }}@${digest}"
echo "Signing ${image} (signatures -> ${COSIGN_REPOSITORY})"
cosign sign --yes "${image}"
done
promote-latest:
# Re-push the :latest tag pointing at the root variant *after* every
# variant matrix job has finished, so GHCR's package version listing
# (sorted by created_at) shows the root image with :latest at the top
# instead of whichever variant happened to finish last.
needs: docker-variant-tags
runs-on: ubuntu-latest
steps:
- name: Normalize image name
id: image-name
run: |
image_name="$(printf '%s' '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')"
printf 'image_name=%s\n' "$image_name" >> "$GITHUB_OUTPUT"
- name: Determine image version
id: version
env:
MANUAL_VERSION: ${{ inputs.version || github.event.inputs.version }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
version="${MANUAL_VERSION#v}"
if [ -z "$version" ] && [ -n "$RELEASE_TAG" ]; then
version="${RELEASE_TAG#v}"
fi
printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Re-tag root image as :latest
if: steps.version.outputs.version != ''
env:
IMAGE: ${{ env.REGISTRY }}/${{ steps.image-name.outputs.image_name }}
VERSION: ${{ steps.version.outputs.version }}
run: |
# Add a unique annotation so the resulting image index manifest gets
# a new digest, which makes GHCR record a fresh package version with
# current timestamp (otherwise the existing root manifest is reused
# and stays where it was in the version listing).
promoted_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
docker buildx imagetools create \
--annotation "index:io.headroom.promoted-at=${promoted_at}" \
--tag "${IMAGE}:latest" \
"${IMAGE}:${VERSION}"