mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
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
220 lines
9.3 KiB
YAML
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}"
|