# Build and push a Docker image with GitHub Actions: tuned template.
#
# What makes this fast and correct, and where each choice is documented:
#   - A docker-container builder via docker/setup-buildx-action. The Engine's
#     default "docker" driver cannot export to the type=gha cache backend;
#     this driver can.
#     https://docs.docker.com/build/cache/backends/gha/
#   - Layer caching with cache-from/cache-to: type=gha, mode=max. mode=max
#     caches intermediate stages too, so multi-stage builds actually hit.
#     https://docs.docker.com/build/cache/backends/
#   - Tags and OCI labels derived by docker/metadata-action instead of a
#     hand-rolled ${{ github.sha }}.
#     https://github.com/docker/metadata-action
#   - Least-privilege permissions: read the repo, write packages (GHCR).
#     https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions
#   - A concurrency block so a new push cancels the in-flight build for the
#     same ref instead of paying for both.
#     https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency
#   - Every action pinned to a full commit SHA (a mutable @v7 tag is a supply-
#     chain risk); the trailing comment keeps the human-readable version.
#     https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions
#
# Adapt it: swap the type=gha cache for a registry cache (commented below) when
# your layers outgrow the repository's Actions-cache budget or you want a cache
# that survives Actions-cache eviction, and keep your Dockerfile multi-stage so
# mode=max caching has stages to restore.
name: Build and push Docker image

on:
  push:
    branches: [main]
    tags: ["v*"]
  pull_request:
    branches: [main]

# Cancel an in-flight build when a newer commit lands on the same ref. Pushes
# to main and PR updates both benefit; nothing waits behind a stale build.
concurrency:
  group: docker-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

# Least privilege: read the checkout, write the image to GHCR. No id-token,
# no broad write. Widen only if a step genuinely needs more.
permissions:
  contents: read
  packages: write

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

      # Creates a docker-container builder. The default "docker" driver cannot
      # export to the type=gha cache backend used below; this one can.
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0

      # Skip the push (and the login) on every pull_request build: fork PRs have
      # no registry write, and an unmerged PR has nothing to publish. The PR
      # still builds, and it still restores the base branch's cache.
      - name: Log in to the Container registry
        if: github.event_name != 'pull_request'
        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Derive tags + OCI labels from the event (branch, tag, sha) so you never
      # hand-maintain a tag expression.
      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=sha,format=long

      - name: Build and push
        uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
        with:
          context: .
          # Build on PRs (restoring the base branch's cache), push only on
          # branch/tag events. Note the cache a PR writes is scoped to that PR:
          # main cannot restore it, so this speeds up the PR, not the merge.
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          # The layer cache. type=gha stores layers in the GitHub Actions cache;
          # mode=max also caches intermediate build stages, so a multi-stage
          # Dockerfile restores its builder layers instead of rebuilding them.
          cache-from: type=gha
          cache-to: type=gha,mode=max
          # Registry-cache alternative: use this instead of the two lines above
          # when your layers outgrow the repository's Actions-cache budget (10 GB
          # by default) or you want a cache that outlives Actions-cache eviction
          # (unused entries are removed after 7 days). It stores the cache as a
          # manifest in your registry:
          #
          #   cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
          #   cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
