GitHub Actions for Docker — Build, Tag, and Push Images 2026
Advertisement
Introduction
Why This Matters
Manually building and pushing Docker images is error-prone and slow. A single misconfigured tag or forgotten push can block a deployment. GitHub Actions automates the entire container pipeline — from code commit to a versioned, verified image sitting in a registry, ready to deploy.
With native integration into GitHub events (push, pull request, tag), you can build images only when needed, cache layers between runs, and push to multiple registries in one workflow.
Pushing to Docker Hub on Git Tags
The most common pattern: build and push a production image only when you create a semantic version tag like v1.2.3:
name: Build and Push to Docker Hub
on:
push:
tags:
- 'v*'
jobs:
build-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKER_USERNAME }}/myapp
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=maxThe docker/metadata-action automatically derives tags from your Git ref — pushing v1.2.3 creates three tags: 1.2.3, 1.2, and sha-abc1234. This eliminates manual tagging errors entirely.
Pushing to GitHub Container Registry (GHCR)
GHCR is free for public repositories and uses your existing GITHUB_TOKEN — no extra secrets required:
name: Build and Push to GHCR
on:
push:
branches: [main]
jobs:
build-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
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: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=maxSet permissions.packages: write at the job level — without it the GITHUB_TOKEN cannot push packages and the workflow fails.
Multi-Platform Builds for ARM and AMD64
Modern infrastructure spans x86 and ARM (Apple Silicon, AWS Graviton). Build for both platforms simultaneously with QEMU emulation:
- 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 multi-platform
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=maxMulti-platform builds increase build time by 2–4x. Consider building only for linux/amd64 on pull requests and doing the full multi-arch build on merge to main.
Layer Caching to Speed Up Builds
Docker layer caching with the GitHub Actions cache backend (type=gha) dramatically reduces rebuild times when only application code changes and base layers are stable:
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=maxFor even faster caching with private registries, use type=registry to store cache in the registry itself:
cache-from: type=registry,ref=ghcr.io/myorg/myapp:cache
cache-to: type=registry,ref=ghcr.io/myorg/myapp:cache,mode=maxBuilding Preview Images on Pull Requests
Build images on PRs but do not push to production tags — this validates the Dockerfile without polluting your registry:
name: PR Docker Build
on:
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build (no push)
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: myapp:pr-${{ github.event.number }}
cache-from: type=gha
cache-to: type=gha,mode=maxSetting push: false validates the build succeeds without publishing the image.
Common Mistakes
- Using Docker Hub password instead of an access token — Create a scoped Docker Hub access token, not your account password. Tokens can be revoked without changing your password.
- Missing
packages: writepermission — GHCR pushes silently fail or throw 403 errors without this permission. - Not using
docker/metadata-action— Hardcoding image tags leads to overwritinglatestunintentionally. Let metadata-action derive tags from Git context. - Building on every file change — Use
pathsfilters to skip Docker builds when only documentation or test files change. - Single-platform images in production — Building only for
amd64will break on ARM-based cloud instances without error until runtime.
Best Practices
- Store
DOCKER_PASSWORDas a GitHub Secret and use scoped access tokens, not account passwords. - Always add
cache-fromandcache-towithtype=gha— it reduces build times by 50–80% for unchanged layers. - Use
docker/metadata-actionto derive consistent, predictable tags from Git refs automatically. - Separate build jobs (PR) from push jobs (merge) to keep registries clean.
- Sign images with
cosignfrom the Sigstore project to verify image provenance before deployment.
Key Takeaways
docker/setup-buildx-actionmust be included beforedocker/build-push-action— Buildx is required for cache and multi-platform support.docker/metadata-actionderives image tags from Git tags and branches automatically, eliminating manual tag management.GITHUB_TOKENis sufficient to push to GHCR — no extra credentials needed whenpackages: writepermission is set.- GitHub Actions cache (
type=gha) for Docker layer caching can reduce build times by 50–80% for stable base layers. - Multi-platform builds (
linux/amd64,linux/arm64) require QEMU setup and increase build duration — best reserved for production merges. - Never store Docker Hub passwords directly — use scoped access tokens that can be revoked independently.
- Setting
push: falseon PR workflows validates the Dockerfile without publishing intermediate images. - Semantic version tags via
docker/metadata-actionenable image rollback to any previous release without guessing tag formats.
Advertisement