Skip to content

Deploy — Docker ​

Builds a multi-platform Docker image and pushes it to GitHub Container Registry (GHCR).

Usage ​

yaml
jobs:
  docker:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/cd-docker.yml@main
    with:
      image-name: my-api
      platforms: linux/amd64,linux/arm64

Inputs ​

InputTypeDefaultDescription
image-namestring''Image name (e.g. my-api). Defaults to the repository name.
contextstring'.'Docker build context (e.g. apps/api)
dockerfilestringDockerfilePath to the Dockerfile, relative to context
platformsstringlinux/amd64,linux/arm64Comma-separated target platforms
pushbooleantruePush image to GHCR
targetstring''Build target stage (e.g. dev, prod)
tag-latestbooleanfalseAlso tag the image as :latest on the default branch
versionstring''Explicit version tag to apply (e.g. v1.2.3). When set, adds a type=raw tag with this value.
build-argsstring''List of Docker build-args (one per line, KEY=VALUE). Never secrets — see below.
provenancestringmode=maxSLSA provenance attached by BuildKit (mode=max, mode=min, false)
sbombooleantrueAttach an SBOM attestation to the image
attestbooleantrueSign a GitHub artifact attestation for the pushed image (only when push)
timeout-minutesnumber60Minutes before the build job is cancelled (GitHub's default is 360) — raise it for a slow emulated build

Secrets ​

SecretRequiredDescription
BUILD_SECRETSNoBuildKit secrets, one id=value per line, read in the Dockerfile with RUN --mount=type=secret,id=<id>

Outputs ​

OutputDescription
imageImage tag(s) applied to the build (newline-separated)
digestImage digest

Tags applied ​

EventTag
Branch push<branch-name>
Pull requestpr-<number>
Semver tag<version>, <major>.<minor>
Any commitsha-<short-sha>
Default branch + tag-latest: truelatest

Notes ​

  • Uses docker/metadata-action for automatic tag and label generation.
  • Build cache is scoped per context using GitHub Actions cache (type=gha).
  • QEMU is registered only when platforms names something other than linux/amd64, the runner's own architecture. An arm64 image built this way runs under emulation, several times slower than a native build: for a large image, build each platform on its own runner (GitHub's ubuntu-24.04-arm label for the arm64 half) and merge the manifests, as Docker documents, or drop linux/arm64 from platforms when nothing deploys on it.
  • timeout-minutes cuts off a build stuck on a pull or an emulated step: without it the job holds the runner (and bills the minutes) for up to 6 hours.
  • Each build adds a sha-* tag, and each platform image and attestation is an untagged version in GHCR: cleanup-ghcr deletes the old ones on a schedule without breaking multi-arch images.
  • The job runs in the production environment on main, otherwise preview.
  • Caller permissions — a reusable workflow gets at most what its caller grants, and GitHub refuses the run if a job asks for more (even when attest is false). Grant on the calling job:
    yaml
    permissions:
      contents: read
      packages: write
      deployments: write
      id-token: write      # attestation signing
      attestations: write
  • Never pass secrets in build-args: they stay in the image history, and provenance: mode=max records their values in an attestation anyone pulling the image can read. Use BUILD_SECRETS (secret mounts) instead.
  • The attestation is signed in a separate job, so the build step (which runs the Dockerfile) never holds an OIDC token. Verify an image with gh attestation verify oci://ghcr.io/<owner>/<image>@<digest> --owner <owner>.