GitHub Actions Tutorial — Project: Docker + GHCR

1 minute read

Project: Docker + GHCR Pipeline

Build, tag, and push multi-platform Docker images to GitHub Container Registry with proper tagging, layer caching, and semver support.


Requirements

Feature Implementation
Build on push to main and tags on: push with branches and tags
Multi-platform (amd64 + arm64) docker/setup-qemu-action + buildx
Smart tagging (SHA, branch, semver) docker/metadata-action
Layer cache cache-from/cache-to with type=gha
Push to GHCR docker/login-action with GHCR

Complete Workflow


name: Docker Build & Push

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

permissions:
  contents: read
  packages: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-qemu-action@v3

      - 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 }}

      - uses: docker/metadata-action@v5
        id: meta
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=sha
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}

      - uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          platforms: linux/amd64,linux/arm64
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max


Tagging Strategy

Trigger Generated Tags
Push to main main, sha-abc1234
Tag v1.2.3 1.2.3, 1.2, 1, sha-abc1234
PR (no push) Build only — no push to registry

Design Decisions

Decision Reasoning
No push on PRs PRs build to verify, but don’t pollute the registry
QEMU + Buildx Required for multi-platform builds
type=gha cache Uses GitHub Actions cache — no external service
Semver pattern tags Users can pin to v1, v1.2, or v1.2.3
packages: write Required to push to GHCR

Expected Output

On push to main:

Tags:
  ghcr.io/my-org/my-app:main
  ghcr.io/my-org/my-app:sha-abc1234
Platforms: linux/amd64, linux/arm64

On tag v1.2.3:

Tags:
  ghcr.io/my-org/my-app:1.2.3
  ghcr.io/my-org/my-app:1.2
  ghcr.io/my-org/my-app:1
  ghcr.io/my-org/my-app:sha-abc1234
Platforms: linux/amd64, linux/arm64

Verification

# Pull the image
docker pull ghcr.io/my-org/my-app:main

# Inspect platforms
docker manifest inspect ghcr.io/my-org/my-app:main

# Run it
docker run --rm ghcr.io/my-org/my-app:main

← Previous: Project — Node.js CI

Next: Project — Multi-Env Deploy →

Updated: