Building and pushing images in CI
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 hubKey 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,
mainpushes:edge, a version tag pushes:1.4.2. - Layer cache is lost between runners. Bring it back with
--cache-fromand--cache-toof typegha. - 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 inspectanswers "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.