Lesson 04 of 9 · Level 1 — Foundations
Build, test and publish container images
A production-grade image pipeline in GitHub Actions: Buildx with layer caching, tags generated from Git (commit SHA, semantic versions), push to GHCR or ECR, vulnerability scanning, capturing the digest, and promoting the new image through a pull request to the GitOps repository.
The pipeline
test → build (Buildx, cached) → push (tags from Git) → scan → record digest → PR to GitOps repo
A packaging line that stamps every box with the order number it came from, checks it for faults, and then puts a form in the manager's tray saying "ready to ship to dev". Shipping itself is the delivery team's job.
Build and push
# .github/workflows/image.yml
name: image
on:
push:
branches: [main]
tags: ["v*.*.*"]
pull_request:
permissions:
contents: read
packages: write # push to GHCR
jobs:
image:
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
if: github.event_name != 'pull_request'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/orders-api
tags: |
type=sha,format=long
type=semver,pattern={{version}}
type=ref,event=pr
- id: build
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }} # PRs build but don't push
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- uses: aquasecurity/trivy-action@0.28.0 # pin a version you tested
if: github.event_name != 'pull_request'
with:
image-ref: ghcr.io/${{ github.repository_owner }}/orders-api@${{ steps.build.outputs.digest }}
severity: CRITICAL,HIGH
exit-code: "1"
ignore-unfixed: true
For Amazon ECR, replace the login with aws-actions/configure-aws-credentials (OIDC, lesson 06) and aws-actions/amazon-ecr-login. For multi-architecture images, add docker/setup-qemu-action and platforms: linux/amd64,linux/arm64.
Pin third-party actions by version tag at minimum, or by full commit SHA for the strongest guarantee (lesson 08).
Promote through the GitOps repository
After the image is pushed and scanned, open a pull request that bumps the image in the dev environment of the config repository:
promote-dev:
needs: image
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
repository: acme/gitops-config
token: ${{ secrets.GITOPS_REPO_TOKEN }} # or a GitHub App token; scoped to that repo only
- run: |
yq -i '.image.digest = "${{ needs.image.outputs.digest }}"' apps/orders-api/envs/dev/values.yaml
- uses: peter-evans/create-pull-request@v7
with:
branch: promote/orders-api-dev-${{ github.sha }}
title: "orders-api: promote ${{ github.sha }} to dev"
body: "Built by ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
The config repository's own rules decide what happens next: auto-merge for dev, required approval for production. Argo CD or Flux then deploys what's merged; the workflow never needs cluster credentials. "GitOps Principles & Practice", lesson 03, covers promotion in depth.
Try it: image to GitOps
- Add the
imageworkflow to a small app repository; open a PR (build only) and merge it (build + push to GHCR). - Push a tag
v0.1.0and confirm the image gets0.1.0alongside thesha-...tag. - Run twice in a row and compare build times with the
ghacache. - Add
promote-devagainst a test config repository and review the pull request it opens.
Recap
- Buildx + layer cache (
type=gha), tags from metadata-action, push only from main/tags. - Scan the pushed digest; fail on serious, fixable findings.
- Capture the digest as a job output; promote via a PR to the GitOps repo, never with cluster credentials.
- Pin actions (tags or SHAs); use OIDC for cloud registries.
This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.