Building and pushing images in CI

Docker · lesson 26 of 31 · 4 min read

Turn a git tag into a pushed, cached, multi-arch image that you can trace back to a commit.

Open this lesson in the learning hub

Key points

  • Build in CI, not on a laptop. What gets deployed should come from a job anyone on the team can re-run and inspect.
  • Derive tags from the git ref: a PR builds only, main pushes :edge, a version tag pushes :1.4.2.
  • Layer cache is lost between runners. Bring it back with --cache-from and --cache-to of type gha.
  • Deploy the digest the build printed, not the tag. It is the only way to know staging and prod ran the same bytes.
  • Keep registry credentials in the CI secret store and use a scoped, push-only robot account.
  • Stamp the image with OCI labels for the commit and build time, so docker inspect answers "where did this come from".

Example

#!/usr/bin/env bash
# ci-publish.sh - the whole publish step, run by the pipeline
set -euo pipefail

IMAGE=ghcr.io/acme/myapp
TAGS="--tag $IMAGE:edge"
case "$GIT_REF" in
  refs/pull/*)      TAGS="" ;;                       # build only, never push
  refs/tags/v*)     TAGS="--tag $IMAGE:$VERSION --tag $IMAGE:latest" ;;
esac

printf "%s" "$REGISTRY_TOKEN" | docker login ghcr.io -u ci-bot --password-stdin

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --cache-from type=gha --cache-to type=gha,mode=max \
  --label org.opencontainers.image.revision="$GIT_SHA" \
  $TAGS --push --metadata-file meta.json .

# deploy this, not the tag
jq -r ".[\"containerimage.digest\"]" meta.json

CI builds it, the registry stores it, and the digest is what you deploy.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Docker course, and every lesson in it is listed on the Docker contents page.