a11oy / .github /workflows /docker-build.yml
betterwithage's picture
sync(space): full source mirror — resolve all GitHub<->Space drift (CTO)
a6a5d8e verified
Raw
History Blame
7.18 kB
name: Container build + GHCR push
# Builds the root Dockerfile, generates an image SBOM, and pushes to GHCR:
# * Pull-request: build + smoke-test only (no push, no registry login).
# * Push to main: build + push SHA-tagged image to ghcr.io/szl-holdings/a11oy.
# * Release published: push semver + latest tags, sign with cosign keyless.
#
# L1 fix (2026-05-31): REVISION build-arg is now passed as github.sha so that
# the runtime ENV A11OY_GIT_SHA is populated in the container and /healthz
# returns the real deployed SHA. Reference: red-team finding L1.
#
# Cosign keyless verification (no stored key; GitHub OIDC + Fulcio + Rekor):
# cosign verify \
# --certificate-identity-regexp \
# "https://github.com/szl-holdings/a11oy/.github/workflows/docker-build.yml.*" \
# --certificate-oidc-issuer https://token.actions.githubusercontent.com \
# ghcr.io/szl-holdings/a11oy:<tag>
#
# References:
# docker/build-push-action: https://github.com/docker/build-push-action
# anchore/sbom-action: https://github.com/anchore/sbom-action
# cosign keyless: https://docs.sigstore.dev/cosign/signing/overview/
#
# Authored for SZL Holdings. Signed-off per repository DCO.
on:
push:
branches: [main]
pull_request:
branches: [main]
release:
types: [published]
permissions:
contents: read
env:
IMAGE: ghcr.io/szl-holdings/a11oy
jobs:
build:
name: Build image + SBOM (push on main + release)
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # push to GHCR on main and release
id-token: write # cosign keyless OIDC token
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Derive image version and tags
id: ver
run: |
SHA7="${GITHUB_SHA::7}"
BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
if [ "${{ github.event_name }}" = "release" ]; then
VERSION="${{ github.event.release.tag_name }}"
TAGS="${{ env.IMAGE }}:${VERSION}
${{ env.IMAGE }}:latest
${{ env.IMAGE }}:sha-${SHA7}"
elif [ "${{ github.event_name }}" = "push" ]; then
VERSION="0.0.0-dev-${SHA7}"
TAGS="${{ env.IMAGE }}:sha-${SHA7}"
else
VERSION="0.0.0-pr-${SHA7}"
TAGS="${{ env.IMAGE }}:pr-${SHA7}"
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "sha7=${SHA7}" >> "$GITHUB_OUTPUT"
echo "build_date=${BUILD_DATE}" >> "$GITHUB_OUTPUT"
# Multi-line value — use heredoc to avoid quoting issues.
{
echo "tags<<EOF"
echo "${TAGS}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
# Log in on push-to-main and on release; skip for PRs.
- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image (push on main + release; load on PR)
id: build
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: .
file: Dockerfile
build-args: |
VERSION=${{ steps.ver.outputs.version }}
REVISION=${{ github.sha }}
BUILD_DATE=${{ steps.ver.outputs.build_date }}
push: ${{ github.event_name != 'pull_request' }}
load: ${{ github.event_name == 'pull_request' }}
tags: ${{ steps.ver.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
# FIX (Yachay, empire-reliability 2026-06-01): docker/build-push-action@v6
# defaults to provenance:true, which exports an OCI attestation manifest.
# Pushing that referrers index to ghcr.io/szl-holdings/* returns 403 Forbidden
# on the attestation blob HEAD (org GHCR rejects the auto-created attestation
# index). Image SBOM is already produced by the dedicated anchore/syft step,
# so disabling buildx attestations is the root-cause fix, not a workaround.
provenance: false
sbom: false
- name: Smoke test image (PR builds loaded into local daemon)
if: github.event_name == 'pull_request'
run: |
TAG="${{ env.IMAGE }}:pr-${{ steps.ver.outputs.sha7 }}"
echo "=== --version ==="
docker run --rm "${TAG}" --version
echo "=== --help ==="
docker run --rm "${TAG}" --help
# FIX (Yachay, empire-reliability 2026-06-01): the SBOM step previously always
# referenced the pushed main tag `:sha-<sha7>`. On pull_request builds the image
# is NOT pushed to GHCR (push:false) — it is `load`ed into the local Docker daemon
# under tag `:pr-<sha7>`. Syft therefore tried to pull `ghcr.io/.../a11oy:sha-<sha7>`
# which does not exist for PRs and returned `unauthorized` (registry has no such
# manifest + no PR login), failing every PR run. Root-cause fix: scan the
# locally-loaded PR image on PRs and the pushed SHA tag on push-to-main.
- name: Generate image SBOM (CycloneDX) via Syft push to main
if: github.event_name == 'push'
uses: anchore/sbom-action@df80a981bc6edbc4e220a492d3cbe9f5547a6e75 # v0.17.9
with:
image: ${{ env.IMAGE }}:sha-${{ steps.ver.outputs.sha7 }}
format: cyclonedx-json
output-file: a11oy-image-sbom.cyclonedx.json
upload-artifact: true
- name: Generate image SBOM (CycloneDX) via Syft PR (local image)
if: github.event_name == 'pull_request'
uses: anchore/sbom-action@df80a981bc6edbc4e220a492d3cbe9f5547a6e75 # v0.17.9
with:
image: ${{ env.IMAGE }}:pr-${{ steps.ver.outputs.sha7 }}
format: cyclonedx-json
output-file: a11oy-image-sbom.cyclonedx.json
upload-artifact: true
- name: Install cosign (release only)
if: github.event_name == 'release'
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
- name: Sign image with cosign keyless (release only)
if: github.event_name == 'release'
env:
COSIGN_EXPERIMENTAL: "1"
run: |
DIGEST="${{ steps.build.outputs.digest }}"
cosign sign --yes "${{ env.IMAGE }}@${DIGEST}"
echo "Signed ${{ env.IMAGE }}@${DIGEST} (keyless OIDC)."
- name: Generate + attach image SBOM on release (signed)
if: github.event_name == 'release'
uses: anchore/sbom-action@df80a981bc6edbc4e220a492d3cbe9f5547a6e75 # v0.17.9
with:
image: ${{ env.IMAGE }}:${{ steps.ver.outputs.version }}
format: cyclonedx-json
output-file: a11oy-image-sbom.cyclonedx.json
upload-artifact: true