---
# === IDENTITY ===
id: software/devops/github-actions-docker/2026
canonical_question: "GitHub Actions reference: Docker build and push"
aliases:
  - "GitHub Actions Docker build push workflow"
  - "Docker build push GitHub Actions GHCR"
  - "GitHub Actions container registry push"
  - "GitHub Actions multi-platform Docker build"
entity_type: software_reference
domain: software > devops > GitHub Actions Docker Build
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-28
confidence: 0.93
version: 1.0
first_published: 2026-02-28

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "docker/build-push-action@v6 released (Jun 2024) with Buildx v0.15+ requirement; GHCR moved to ghcr.io namespace (2021)"
  next_review: 2026-08-27
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "NEVER hardcode registry credentials in workflow files -- use GitHub Secrets or GITHUB_TOKEN"
  - "Always use docker/setup-buildx-action before docker/build-push-action -- Buildx is required for multi-platform builds and advanced caching"
  - "Pin Docker action versions to major tags (e.g., docker/build-push-action@v6) -- never use @main"
  - "Set packages:write permission explicitly when pushing to GHCR -- GITHUB_TOKEN needs this scope"
  - "Multi-platform builds with QEMU emulation are 3-10x slower than native builds -- use matrix strategy with native runners for production"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need Node.js CI/CD pipeline without Docker"
    use_instead: "software/devops/github-actions-nodejs/2026"
  - condition: "Need Dockerfile optimization and best practices"
    use_instead: "software/devops/dockerfile-best-practices/2026"
  - condition: "Need GitLab CI container builds"
    use_instead: "software/devops/gitlab-ci-pipeline/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "registry"
    question: "Which container registry do you want to push to?"
    type: choice
    options: ["GitHub Container Registry (ghcr.io)", "Docker Hub", "AWS ECR", "Google Artifact Registry", "Azure Container Registry"]
  - key: "platforms"
    question: "Do you need multi-platform images (e.g., amd64 + arm64)?"
    type: choice
    options: ["amd64 only", "amd64 + arm64", "custom platform list"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/devops/github-actions-docker/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-28)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/devops/dockerfile-best-practices/2026"
      label: "Dockerfile Best Practices (Multi-Stage Builds)"
    - id: "software/devops/github-actions-nodejs/2026"
      label: "GitHub Actions Node.js CI/CD Pipeline"
  solves: []
  alternative_to:
    - id: "software/devops/gitlab-ci-pipeline/2026"
      label: "GitLab CI Basic Pipeline"
  often_confused_with:
    - id: "software/devops/dockerfile-best-practices/2026"
      label: "Dockerfile Best Practices (Multi-Stage Builds)"

# === SOURCES ===
sources:
  - id: src1
    title: "Docker Build GitHub Actions"
    author: Docker
    url: https://docs.docker.com/build/ci/github-actions/
    type: official_docs
    published: 2024-06-01
    reliability: authoritative
  - id: src2
    title: "docker/build-push-action: GitHub Action to build and push Docker images"
    author: Docker
    url: https://github.com/docker/build-push-action
    type: official_docs
    published: 2024-06-01
    reliability: authoritative
  - id: src3
    title: "Working with the Container registry"
    author: GitHub
    url: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry
    type: official_docs
    published: 2024-01-15
    reliability: authoritative
  - id: src4
    title: "Multi-platform image with GitHub Actions"
    author: Docker
    url: https://docs.docker.com/build/ci/github-actions/multi-platform/
    type: official_docs
    published: 2024-06-01
    reliability: authoritative
  - id: src5
    title: "docker/metadata-action: GitHub Action to extract metadata"
    author: Docker
    url: https://github.com/docker/metadata-action
    type: official_docs
    published: 2024-06-01
    reliability: authoritative
  - id: src6
    title: "GitHub Actions and Docker"
    author: Docker
    url: https://docs.docker.com/guides/gha/
    type: official_docs
    published: 2024-06-01
    reliability: authoritative
  - id: src7
    title: "Security hardening for GitHub Actions"
    author: GitHub
    url: https://docs.github.com/en/actions/security-for-github-actions/security-hardening-for-github-actions
    type: official_docs
    published: 2024-01-15
    reliability: authoritative
---

# GitHub Actions: Docker Build and Push

## TL;DR

- **Bottom line**: Use Docker's official GitHub Actions (`docker/build-push-action@v6`, `docker/metadata-action@v5`, `docker/setup-buildx-action@v3`) to build, tag, and push container images to any registry (GHCR, Docker Hub, ECR) with layer caching and multi-platform support.
- **Key tool/command**: `docker/build-push-action@v6` with `push: true` and `tags` from `docker/metadata-action@v5`.
- **Watch out for**: Not setting up Buildx before building -- without it you cannot use build cache, multi-platform builds, or advanced Dockerfile features.
- **Works with**: Any Dockerfile, any OCI-compliant registry, linux/amd64 and linux/arm64 platforms.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NEVER hardcode registry credentials in workflow files -- use GitHub Secrets or `GITHUB_TOKEN`.
- Always use `docker/setup-buildx-action@v3` before `docker/build-push-action` -- Buildx is required for layer caching and multi-platform builds.
- Set `packages: write` permission explicitly when pushing to GHCR -- `GITHUB_TOKEN` needs this scope.
- Pin Docker action versions to major tags (e.g., `docker/build-push-action@v6`) -- never use `@main`.
- Multi-platform builds with QEMU emulation are 3-10x slower -- use matrix strategy with native runners for CI speed.
- Docker Hub rate limits: 100 pulls/6h for anonymous, 200 pulls/6h for authenticated. Use GHCR or private registry for high-volume CI.

## Quick Reference

| Action | Version | Purpose | Key Inputs |
|---|---|---|---|
| `docker/setup-qemu-action` | `@v3` | Enable multi-platform builds via QEMU | `platforms: linux/amd64,linux/arm64` |
| `docker/setup-buildx-action` | `@v3` | Create Buildx builder instance | `driver-opts`, `buildkitd-flags` |
| `docker/login-action` | `@v3` | Authenticate to container registry | `registry`, `username`, `password` |
| `docker/metadata-action` | `@v5` | Generate tags and labels from Git | `images`, `tags` (type=ref, type=sha, type=semver) |
| `docker/build-push-action` | `@v6` | Build and push Docker image | `context`, `push`, `tags`, `platforms`, `cache-from/to` |
| GHCR login | `registry: ghcr.io` | GitHub Container Registry | `username: ${{ github.actor }}`, `password: ${{ secrets.GITHUB_TOKEN }}` |
| Docker Hub login | `registry: docker.io` | Docker Hub | `username`, `password: ${{ secrets.DOCKERHUB_TOKEN }}` |
| ECR login | `aws-actions/amazon-ecr-login@v2` | AWS Elastic Container Registry | Requires AWS credentials |
| Cache (inline) | `cache-from: type=gha` | GitHub Actions cache backend | `cache-to: type=gha,mode=max` |
| Cache (registry) | `cache-from: type=registry` | Use registry as cache | `cache-to: type=registry,ref=...` |
| Multi-platform | `platforms: linux/amd64,linux/arm64` | Build for multiple architectures | Requires QEMU or native runners |

## Decision Tree

```
START
├── Single platform (amd64 only)?
│   ├── YES → setup-buildx + login + metadata + build-push (no QEMU needed)
│   └── NO ↓
├── Multi-platform (amd64 + arm64)?
│   ├── YES → Add setup-qemu + platforms: linux/amd64,linux/arm64
│   └── NO ↓
├── Push to GHCR?
│   ├── YES → login with GITHUB_TOKEN, registry: ghcr.io
│   └── NO ↓
├── Push to Docker Hub?
│   ├── YES → login with DOCKERHUB_TOKEN
│   └── NO ↓
├── Push to AWS ECR?
│   ├── YES → aws-actions/configure-aws-credentials + amazon-ecr-login
│   └── NO ↓
└── DEFAULT → Build without push (CI validation only): push: false
```

## Step-by-Step Guide

### 1. Create the workflow file

Create `.github/workflows/docker.yml` for building and pushing Docker images. [src1]

```yaml
name: Docker Build and 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

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
```

**Verify**: Push to main → Actions tab → see image pushed to ghcr.io → `docker pull ghcr.io/<owner>/<repo>:main`.

### 2. Configure semantic versioning tags

Use `docker/metadata-action` to automatically tag images based on Git tags, branches, and SHAs. [src5]

```yaml
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=
```

**Verify**: Push tag `v1.2.3` → image gets tags: `1.2.3`, `1.2`, and the Git SHA.

### 3. Add multi-platform support

Enable QEMU emulation for building images targeting multiple architectures. [src4]

```yaml
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
```

**Verify**: `docker manifest inspect ghcr.io/<owner>/<repo>:main` → shows both `amd64` and `arm64` manifests.

### 4. Enable GitHub Actions cache

Use the GitHub Actions cache backend for layer caching across workflow runs. [src1]

```yaml
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
```

**Verify**: Second build shows "importing cache manifest" in logs and completes faster.

### 5. Push to Docker Hub

For public images, push to Docker Hub with a separate login step. [src6]

```yaml
      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}
```

**Verify**: Check Docker Hub repository for new tags after push.

### 6. Push to AWS ECR

For AWS deployments, authenticate with ECR using OIDC or access keys. [src1]

```yaml
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: us-east-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.login-ecr.outputs.registry }}/my-app:${{ github.sha }}
```

**Verify**: `aws ecr describe-images --repository-name my-app` → shows new image.

## Code Examples

### Complete GHCR workflow with caching and semantic versioning

> Full script: [complete-ghcr-workflow-with-caching-and-semantic-v.yml](scripts/complete-ghcr-workflow-with-caching-and-semantic-v.yml) (50 lines)

```yaml
# .github/workflows/docker.yml
# Input:  Repository with Dockerfile at root
# Output: Multi-tagged container image on ghcr.io
name: Docker Build & Push
on:
# ... (see full script)
```

### Multi-platform build with QEMU

> Full script: [multi-platform-build-with-qemu.yml](scripts/multi-platform-build-with-qemu.yml) (40 lines)

```yaml
# .github/workflows/docker-multiplatform.yml
# Input:  Multi-arch compatible Dockerfile
# Output: linux/amd64 + linux/arm64 manifest on GHCR
name: Multi-Platform Docker Build
on:
# ... (see full script)
```

### CI-only build (no push) for pull requests

```yaml
# .github/workflows/docker-ci.yml
# Input:  Dockerfile to validate
# Output: Build success/failure status on PR

name: Docker CI

on:
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - name: Build (no push)
        uses: docker/build-push-action@v6
        with:
          context: .
          push: false
          tags: ci-test:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
```

## Anti-Patterns

### Wrong: Building without Buildx

```yaml
# ❌ BAD — docker build without Buildx cannot use layer caching or multi-platform
steps:
  - run: docker build -t myimage:latest .
  - run: docker push myimage:latest
```

### Correct: Using Docker official actions with Buildx

```yaml
# ✅ GOOD — Buildx enables caching, multi-platform, and advanced features
steps:
  - uses: docker/setup-buildx-action@v3
  - uses: docker/build-push-action@v6
    with:
      context: .
      push: true
      tags: myimage:latest
      cache-from: type=gha
      cache-to: type=gha,mode=max
```

### Wrong: Hardcoding image tags

```yaml
# ❌ BAD — hardcoded tags don't track versions, branches, or SHAs
- uses: docker/build-push-action@v6
  with:
    tags: ghcr.io/myorg/myapp:latest
```

### Correct: Using metadata-action for dynamic tags

```yaml
# ✅ GOOD — generates tags from Git refs, semver, and SHA automatically
- uses: docker/metadata-action@v5
  id: meta
  with:
    images: ghcr.io/${{ github.repository }}
    tags: |
      type=semver,pattern={{version}}
      type=ref,event=branch
      type=sha
- uses: docker/build-push-action@v6
  with:
    tags: ${{ steps.meta.outputs.tags }}
```

### Wrong: Pushing images on pull requests

```yaml
# ❌ BAD — PR builds should validate, not push (especially from forks)
- uses: docker/build-push-action@v6
  with:
    push: true  # Always pushes, even on PRs
```

### Correct: Conditional push based on event type

```yaml
# ✅ GOOD — only push on push events, build-only on PRs
- uses: docker/build-push-action@v6
  with:
    push: ${{ github.event_name != 'pull_request' }}
```

## Common Pitfalls

- **Missing packages:write permission**: Pushing to GHCR fails with 403. Fix: add `permissions: packages: write` at job or workflow level. [src3]
- **GHA cache exceeding limit**: GitHub Actions cache has a 10 GB limit per repo. Fix: use `mode=max` sparingly or switch to registry cache (`type=registry`). [src1]
- **QEMU builds extremely slow**: ARM64 builds under emulation take 5-20 minutes. Fix: use native `arm64` runners or split into a matrix with `runs-on: [self-hosted, arm64]`. [src4]
- **Image not visible in GHCR**: New packages default to private. Fix: go to Package Settings → Change visibility → Public. [src3]
- **Docker Hub rate limits in CI**: Anonymous pulls limited to 100/6h. Fix: add `docker/login-action` even for pull-only workflows to get 200/6h. [src6]
- **Build context too large**: Entire repo sent as build context. Fix: use `.dockerignore` to exclude `node_modules/`, `.git/`, test fixtures. [src2]

## Diagnostic Commands

```bash
# Check Docker version on runner
docker version

# Inspect Buildx builders
docker buildx ls

# Check image manifest (multi-platform)
docker manifest inspect ghcr.io/owner/repo:tag

# List GHCR packages via CLI
gh api user/packages?package_type=container

# Check image size and layers
docker image inspect ghcr.io/owner/repo:tag --format '{{.Size}}'

# View GHA cache usage
gh cache list

# Debug Buildx build locally
docker buildx build --progress=plain --no-cache .
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| docker/build-push-action@v6 | Current | Requires Buildx v0.15+ | Update setup-buildx-action to @v3 |
| docker/build-push-action@v5 | Deprecated | — | Replace @v5 with @v6 |
| docker/metadata-action@v5 | Current | New tag type syntax | Review tag configuration |
| docker/login-action@v3 | Current | — | — |
| docker/setup-buildx-action@v3 | Current | — | — |
| docker/setup-qemu-action@v3 | Current | — | — |
| GHCR (ghcr.io) | Stable | — | Replaced docker.pkg.github.com |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Building container images in GitHub CI | Building images in GitLab CI | GitLab CI kaniko or Docker-in-Docker |
| Pushing to GHCR, Docker Hub, or ECR | Need complex image pipeline (scanning, signing, SBOM) | Tekton Pipelines or Harbor |
| Need multi-platform images | Need GPU-accelerated builds | Self-hosted runners with GPU |
| Want layer caching across CI runs | Building non-Docker artifacts (npm, PyPI) | Language-specific CI workflows |
| Simple to moderate container workflows | Need 50+ image variants per build | Custom build matrix with self-hosted runners |

## Important Caveats

- GitHub Actions cache (`type=gha`) has a 10 GB limit per repository shared across all workflows. Old cache entries are evicted LRU.
- GHCR packages inherit repository visibility by default but can be changed independently. Private packages count against GitHub Packages storage limits.
- Multi-platform images built with QEMU may exhibit subtle behavior differences vs. native builds -- always test on real hardware for production ARM deployments.
- `docker/build-push-action` builds inside the runner, not inside a container. The runner must have Docker installed (default on ubuntu-latest).
- The `latest` tag should only be applied to production-ready releases, not to every push to main.

## Related Units

- [Dockerfile Best Practices (Multi-Stage Builds)](/software/devops/dockerfile-best-practices/2026)
- [GitHub Actions: Node.js CI/CD Pipeline](/software/devops/github-actions-nodejs/2026)
- [GitLab CI: Basic Pipeline](/software/devops/gitlab-ci-pipeline/2026)
