
Docker Impl Cicd
- 14 installs
- 9 repo stars
- Updated July 8, 2026
- openaec-foundation/docker-claude-skill-package
Helps with devops & ci/cd tasks.
About
docker-impl-cicd is a Claude Code skill for devops & ci/cd. It helps solo builders move faster with AI-assisted development.
- docker-impl-cicd
- DevOps & CI/CD
- AI-coding skill
Docker Impl Cicd by the numbers
- 14 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #958 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/docker-claude-skill-package --skill docker-impl-cicdAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 14 |
|---|---|
| repo stars | ★ 9 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/docker-claude-skill-package ↗ |
What it does
Helps with devops & ci/cd tasks.
Files
docker-impl-cicd
Quick Reference
GitHub Actions Docker Toolkit
| Action | Purpose | Required |
|---|---|---|
docker/setup-buildx-action@v3 | Install and configure buildx builder | ALWAYS |
docker/setup-qemu-action@v3 | Install QEMU for multi-platform builds | Only for multi-arch |
docker/login-action@v3 | Authenticate to container registries | ALWAYS before push |
docker/build-push-action@v6 | Build and push images with BuildKit | ALWAYS |
docker/metadata-action@v5 | Generate tags and labels from Git context | ALWAYS |
Registry Authentication Comparison
| Registry | Login Server | Username Secret | Password Secret |
|---|---|---|---|
| Docker Hub | (default) | DOCKERHUB_USERNAME | DOCKERHUB_TOKEN |
| GHCR | ghcr.io | github.actor | secrets.GITHUB_TOKEN |
| AWS ECR | <account>.dkr.ecr.<region>.amazonaws.com | AWS_ACCESS_KEY_ID | AWS_SECRET_ACCESS_KEY |
| Azure ACR | <name>.azurecr.io | ACR_USERNAME | ACR_PASSWORD |
| Google GAR | <region>-docker.pkg.dev | _json_key | Service account JSON |
Cache Strategy Decision Tree
Is this a GitHub Actions workflow?
├── YES → Use type=gha (fastest, no registry auth needed)
│ └── ALWAYS set mode=max for full intermediate layer caching
├── NO → Is a container registry available?
│ ├── YES → Use type=registry with a dedicated cache tag
│ │ └── ALWAYS use mode=max for CI builds
│ └── NO → Use type=local with mounted volume
└── Need to share cache across forks/PRs?
└── Use type=registry (gha cache is scoped to branch)Image Tagging Conventions
| Trigger | Tag Pattern | Example |
|---|---|---|
| Push to main | latest, main | myapp:latest |
| Git tag (semver) | v1.2.3, 1.2.3, 1.2, 1 | myapp:1.2.3 |
| Pull request | pr-<number> | myapp:pr-42 |
| Branch push | <branch-name> | myapp:feature-auth |
| Git SHA | sha-<short-sha> | myapp:sha-a1b2c3d |
Critical Warnings
NEVER hardcode registry credentials in workflow files or Dockerfiles. ALWAYS use GitHub Secrets or OIDC for authentication.
NEVER use docker login with plaintext passwords in CI. ALWAYS use docker/login-action which handles credential storage securely.
NEVER push images without cache configuration in CI. Without --cache-from/--cache-to, every CI build starts from scratch, wasting minutes.
NEVER use type=gha cache with mode=min (the default). ALWAYS set mode=max to cache all intermediate layers, not just exported layers.
NEVER build multi-platform images with --load. Multi-platform manifests can only be pushed to a registry (--push) or exported to a file.
ALWAYS pin GitHub Action versions to major tags (e.g., @v3) at minimum. Pin to SHA for maximum security in production workflows.
---
Complete Build-and-Push Workflow
name: Build and Push Docker Image
on:
push:
branches: [main]
tags: ["v*.*.*"]
pull_request:
branches: [main]
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: |
user/myapp
ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
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---
Multi-Platform Build Setup
Platform Support Matrix
| Platform | QEMU Required | Common Use Case |
|---|---|---|
linux/amd64 | No (native on most CI) | Standard x86-64 servers |
linux/arm64 | Yes | AWS Graviton, Apple Silicon, Raspberry Pi 4+ |
linux/arm/v7 | Yes | Raspberry Pi 3, older ARM devices |
linux/arm/v6 | Yes | Raspberry Pi Zero/1 |
linux/386 | Yes | Legacy 32-bit systems |
linux/s390x | Yes | IBM mainframes |
linux/ppc64le | Yes | IBM POWER systems |
Cross-Compilation Pattern (Faster than QEMU)
For compiled languages, cross-compile on the native platform instead of emulating:
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:1.22 AS build
ARG TARGETOS TARGETARCH
WORKDIR /src
COPY go.* ./
RUN go mod download
COPY . .
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 \
go build -o /app ./cmd
FROM alpine:3.21
COPY --from=build /app /usr/bin/app
USER 1001
ENTRYPOINT ["/usr/bin/app"]ALWAYS use --platform=$BUILDPLATFORM on the build stage and cross-compile with TARGETOS/TARGETARCH. This avoids QEMU emulation for the compilation step, reducing build time by 5-10x.
---
Cache Strategies in CI
GitHub Actions Cache (type=gha)
- uses: docker/build-push-action@v6
with:
cache-from: type=gha
cache-to: type=gha,mode=max- Uses GitHub Actions cache service (same as
actions/cache). - Scoped to the current branch; falls back to the default branch.
- 10 GB limit per repository. Old entries are evicted automatically.
- ALWAYS use
mode=maxto cache all intermediate layers.
Registry Cache (type=registry)
- uses: docker/build-push-action@v6
with:
cache-from: type=registry,ref=user/myapp:buildcache
cache-to: type=registry,ref=user/myapp:buildcache,mode=max- Stored as a separate image manifest in the registry.
- Shared across all branches, PRs, forks, and CI providers.
- Requires registry authentication.
- ALWAYS use a dedicated cache tag (e.g.,
:buildcache), not:latest.
Multi-Branch Cache Strategy
cache-from: |
type=registry,ref=user/myapp:cache-${{ github.ref_name }}
type=registry,ref=user/myapp:cache-main
cache-to: type=registry,ref=user/myapp:cache-${{ github.ref_name }},mode=maxThis pattern caches per-branch with a fallback to main, ensuring feature branches benefit from the main branch cache.
---
Docker Scout in CI
- name: Docker Scout CVE scan
uses: docker/scout-action@v1
with:
command: cves
image: ${{ steps.meta.outputs.tags }}
only-severities: critical,high
exit-code: trueexit-code: truefails the workflow if critical/high vulnerabilities are found.- ALWAYS run Scout after building but before deploying to production.
- Use
sarifoutput format for GitHub Security tab integration.
---
metadata-action Tag Types
| Type | Input | Output Tag |
|---|---|---|
type=semver,pattern={{version}} | Tag v1.2.3 | 1.2.3 |
type=semver,pattern={{major}}.{{minor}} | Tag v1.2.3 | 1.2 |
type=semver,pattern={{major}} | Tag v1.2.3 | 1 |
type=ref,event=branch | Push to main | main |
type=ref,event=pr | PR #42 | pr-42 |
type=sha,prefix=sha- | Any commit | sha-a1b2c3d |
type=schedule | Cron trigger | nightly |
type=raw,value=latest | Manual | latest |
type=edge | Default branch push | edge |
---
Security Best Practices for CI/CD
| Practice | Implementation |
|---|---|
| NEVER hardcode credentials | Use secrets.* in GitHub Actions |
| ALWAYS use access tokens | Docker Hub: Personal Access Token, not password |
| ALWAYS scope permissions | permissions: packages: write only when needed |
| NEVER push from PRs | Guard with if: github.event_name != 'pull_request' |
| ALWAYS pin action versions | Use @v3 or full SHA for supply chain security |
| ALWAYS scan images | Run Docker Scout or Trivy before deployment |
| NEVER store secrets in ARG/ENV | Use --secret flag in build |
| ALWAYS use OIDC when possible | Keyless auth for AWS ECR, GCP GAR |
---
Reference Links
- references/github-actions.md -- Complete GitHub Actions workflow examples for all registry types
- references/examples.md -- Multi-platform builds, registry auth, cache strategies
- references/anti-patterns.md -- CI/CD mistakes and how to avoid them
Official Sources
- https://docs.docker.com/build/ci/github-actions/
- https://github.com/docker/build-push-action
- https://github.com/docker/metadata-action
- https://github.com/docker/login-action
- https://github.com/docker/setup-buildx-action
- https://docs.docker.com/build/cache/backends/gha/
- https://docs.docker.com/build/building/multi-platform/
- https://docs.docker.com/scout/integrations/ci/gha/
CI/CD Anti-Patterns
AP-01: Hardcoded Registry Credentials
# BAD: Credentials in plaintext in workflow file
- name: Login
run: docker login -u myuser -p mypassword123
# BAD: Credentials in environment variables defined in the workflow
env:
DOCKER_PASSWORD: supersecretWhy it fails: Workflow files are committed to the repository. Anyone with read access sees the credentials. Credentials in logs are partially masked but not reliably.
ALWAYS do this instead:
# GOOD: Use GitHub Secrets
- uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}---
AP-02: No Cache Configuration
# BAD: Every build starts from scratch
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: user/myapp:latest
# No cache-from or cache-toWhy it fails: Without cache, every CI build downloads all base image layers, reinstalls all dependencies, and recompiles everything. A 2-minute cached build becomes a 15-minute full build.
ALWAYS do this instead:
# GOOD: GitHub Actions cache with mode=max
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: user/myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max---
AP-03: Using mode=min Cache (the Default)
# BAD: mode=min only caches final exported layers
cache-to: type=gha
# This is equivalent to:
cache-to: type=gha,mode=minWhy it fails: mode=min only caches layers that appear in the final image. Multi-stage build intermediate layers (dependency installation, compilation) are NOT cached. The most expensive steps are rebuilt every time.
ALWAYS do this instead:
# GOOD: mode=max caches ALL intermediate layers
cache-to: type=gha,mode=max---
AP-04: Pushing Images from Pull Requests
# BAD: Pushes from any event, including PRs
- uses: docker/build-push-action@v6
with:
push: true
tags: user/myapp:latestWhy it fails: Pull requests from forks can inject malicious code. Pushing from PRs allows anyone to overwrite your production image tags by opening a PR.
ALWAYS do this instead:
# GOOD: Only push on non-PR events
- uses: docker/build-push-action@v6
with:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}---
AP-05: Using docker build Instead of buildx
# BAD: Legacy builder, no cache export, no multi-platform
- run: docker build -t user/myapp:latest .
- run: docker push user/myapp:latestWhy it fails: The legacy docker build command does not support cache backends, multi-platform builds, build secrets, or SBOM/provenance attestations. It is functionally inferior in every CI scenario.
ALWAYS do this instead:
# GOOD: Use setup-buildx-action + build-push-action
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: user/myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max---
AP-06: Multi-Platform Build with --load
# BAD: --load does not work with multi-platform
- uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
load: true # ERROR: multi-platform build cannot be loadedWhy it fails: The local Docker image store only supports a single platform per tag. Multi-platform manifests (manifest lists) can only exist in a registry.
Do this instead:
# GOOD: Push multi-platform to registry
- uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true
tags: user/myapp:latest
# GOOD: Load single platform locally (for testing)
- uses: docker/build-push-action@v6
with:
load: true
tags: myapp:test---
AP-07: Missing QEMU Setup for Multi-Platform
# BAD: No QEMU — arm64 build fails
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
# Fails: arm64 emulation not availableWhy it fails: GitHub Actions runners are x86-64. Building for arm64 or other architectures requires QEMU user-space emulation, which must be installed explicitly.
ALWAYS do this instead:
# GOOD: Install QEMU before buildx
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64---
AP-08: Using QEMU When Cross-Compilation is Possible
# BAD: QEMU emulates entire Go compilation — extremely slow
# Dockerfile:
FROM golang:1.22
COPY . .
RUN go build -o /appWhy it fails: QEMU emulation is 5-10x slower than native execution. For compiled languages (Go, Rust, C/C++), the compiler can target other architectures natively without emulation.
ALWAYS do this instead for compiled languages:
# GOOD: Cross-compile on native platform
FROM --platform=$BUILDPLATFORM golang:1.22 AS build
ARG TARGETOS TARGETARCH
WORKDIR /src
COPY . .
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o /app
FROM alpine:3.21
COPY --from=build /app /usr/bin/app---
AP-09: Manual Tag Management
# BAD: Manually construct tags — error-prone and incomplete
- run: |
VERSION=${GITHUB_REF#refs/tags/v}
docker tag myapp user/myapp:$VERSION
docker tag myapp user/myapp:latest
docker push user/myapp:$VERSION
docker push user/myapp:latestWhy it fails: Manual tag logic is fragile, misses edge cases (PRs, branches, SHA tags), and does not generate OCI-compliant labels. Different workflows implement tagging differently, causing inconsistency.
ALWAYS do this instead:
# GOOD: Use metadata-action for consistent, automatic tagging
- uses: docker/metadata-action@v5
id: meta
with:
images: user/myapp
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
- uses: docker/build-push-action@v6
with:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}---
AP-10: Secrets via Build Arguments
# BAD: Secret visible in docker history
- uses: docker/build-push-action@v6
with:
build-args: |
NPM_TOKEN=${{ secrets.NPM_TOKEN }}
DATABASE_URL=${{ secrets.DATABASE_URL }}Why it fails: Build arguments are recorded in image metadata and visible via docker history --no-trunc. Anyone who pulls the image can extract the secrets.
ALWAYS do this instead:
# GOOD: Use secret mounts — not recorded in image layers
- uses: docker/build-push-action@v6
with:
secrets: |
"npm_token=${{ secrets.NPM_TOKEN }}"RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci---
AP-11: No Vulnerability Scanning
# BAD: Build and push without scanning
- uses: docker/build-push-action@v6
with:
push: true
tags: user/myapp:latest
# No security scan before deploymentWhy it fails: Vulnerable base images and dependencies are silently promoted to production. Known CVEs with available patches go undetected.
ALWAYS do this instead:
# GOOD: Scan before push
- uses: docker/build-push-action@v6
with:
load: true
tags: myapp:scan
- uses: docker/scout-action@v1
with:
command: cves
image: local://myapp:scan
only-severities: critical,high
exit-code: true
- uses: docker/build-push-action@v6
if: success()
with:
push: true
tags: user/myapp:latest---
AP-12: No Cache Scope in Matrix Builds
# BAD: All matrix jobs share the same cache, overwriting each other
strategy:
matrix:
service: [api, worker, web]
steps:
- uses: docker/build-push-action@v6
with:
file: ${{ matrix.service }}/Dockerfile
cache-from: type=gha
cache-to: type=gha,mode=maxWhy it fails: Without scope, all three matrix jobs read and write to the same cache namespace. The last job to finish overwrites the cache of the other two. Only one service benefits from caching.
ALWAYS do this instead:
# GOOD: Scoped cache per matrix entry
- uses: docker/build-push-action@v6
with:
file: ${{ matrix.service }}/Dockerfile
cache-from: type=gha,scope=${{ matrix.service }}
cache-to: type=gha,scope=${{ matrix.service }},mode=max---
AP-13: Using latest Tag as the Only Tag
# BAD: Only latest — no way to pin or rollback
tags: user/myapp:latestWhy it fails: latest is mutable. If a bad version is pushed, there is no way to roll back to the previous version. Kubernetes deployments with imagePullPolicy: Always silently pick up breaking changes.
ALWAYS do this instead:
# GOOD: Immutable semver tags + latest for convenience
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=sha-
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}---
AP-14: Skipping Buildx Setup
# BAD: Relying on the default Docker builder in CI
- run: docker build -t myapp .Why it fails: Without docker/setup-buildx-action, the builder does not support:
- Cache export/import (
--cache-from/--cache-to) - Multi-platform builds (
--platform) - Build secrets (
--secret) - SBOM and provenance attestations
- Parallel stage execution
ALWAYS do this instead:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
# All BuildKit features now available---
AP-15: Not Using Provenance/SBOM
# BAD: No supply chain metadata
- uses: docker/build-push-action@v6
with:
push: true
tags: user/myapp:latest
provenance: falseWhy it fails: Without provenance and SBOM attestations, there is no verifiable record of how the image was built, what tools were used, or what packages it contains. This blocks compliance with SLSA and other supply chain security frameworks.
ALWAYS do this instead for production images:
- uses: docker/build-push-action@v6
with:
push: true
tags: user/myapp:latest
provenance: mode=max
sbom: true---
Summary Table
| # | Anti-Pattern | Impact | Fix |
|---|---|---|---|
| AP-01 | Hardcoded credentials | Credential leak | GitHub Secrets + login-action |
| AP-02 | No cache | Slow builds (5-15 min) | type=gha,mode=max |
| AP-03 | mode=min cache | Partial cache hits | mode=max |
| AP-04 | Push from PRs | Malicious image injection | Guard with event check |
| AP-05 | Legacy docker build | No modern features | setup-buildx + build-push-action |
| AP-06 | Multi-platform + load | Build failure | Push to registry |
| AP-07 | Missing QEMU | Cross-platform failure | setup-qemu-action |
| AP-08 | QEMU for compiled langs | 5-10x slower builds | Cross-compilation |
| AP-09 | Manual tags | Inconsistent, fragile | metadata-action |
| AP-10 | Secrets in build-args | Secret exposure | Secret mounts |
| AP-11 | No scanning | Vulnerable images in prod | Docker Scout / Trivy |
| AP-12 | No cache scope | Cache thrashing in matrix | Scoped cache |
| AP-13 | Only latest tag | No rollback | Semver + SHA tags |
| AP-14 | No buildx setup | Missing BuildKit features | setup-buildx-action |
| AP-15 | No provenance/SBOM | No supply chain proof | Enable attestations |
---
Official Sources
- https://docs.docker.com/build/ci/github-actions/
- https://docs.docker.com/build/cache/backends/gha/
- https://docs.docker.com/build/building/multi-platform/
- https://github.com/docker/build-push-action
- https://github.com/docker/metadata-action
CI/CD Examples: Multi-Platform, Registry Auth, Cache Strategies
Multi-Platform Build Examples
Basic Multi-Platform (amd64 + arm64)
- 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: user/myapp:latestQEMU is required for any platform that does not match the runner's native architecture. GitHub Actions runners are linux/amd64, so linux/arm64 and all other platforms require QEMU.
Cross-Compilation Dockerfile (Go)
Cross-compilation is 5-10x faster than QEMU emulation for compiled languages.
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS build
# These ARGs are automatically set by BuildKit
ARG TARGETOS TARGETARCH
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 \
go build -ldflags="-s -w" -o /app ./cmd/server
FROM alpine:3.21
RUN apk --no-cache add ca-certificates
COPY --from=build /app /usr/bin/app
USER 1001
ENTRYPOINT ["/usr/bin/app"]Key points:
--platform=$BUILDPLATFORMruns the build stage on the CI runner's native arch.TARGETOSandTARGETARCHare set automatically by BuildKit for each target platform.CGO_ENABLED=0is required for static cross-compilation without C dependencies.- The runtime stage runs on the target platform natively.
Cross-Compilation Dockerfile (Rust)
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM rust:1.77-alpine AS build
ARG TARGETARCH
RUN apk add --no-cache musl-dev
# Install cross-compilation target
RUN case "$TARGETARCH" in \
amd64) RUST_TARGET="x86_64-unknown-linux-musl" ;; \
arm64) RUST_TARGET="aarch64-unknown-linux-musl" ;; \
*) echo "Unsupported: $TARGETARCH" && exit 1 ;; \
esac && \
rustup target add "$RUST_TARGET" && \
echo "$RUST_TARGET" > /rust-target.txt
WORKDIR /src
COPY Cargo.toml Cargo.lock ./
RUN --mount=type=cache,target=/usr/local/cargo/registry \
mkdir src && echo "fn main() {}" > src/main.rs && \
cargo build --release --target $(cat /rust-target.txt) && \
rm -rf src
COPY . .
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/src/target \
cargo build --release --target $(cat /rust-target.txt) && \
cp target/$(cat /rust-target.txt)/release/myapp /app
FROM alpine:3.21
COPY --from=build /app /usr/bin/app
USER 1001
ENTRYPOINT ["/usr/bin/app"]Node.js Multi-Platform (Interpreted Language)
Interpreted languages do not need cross-compilation. QEMU emulates the target platform for native dependency installation.
# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --production
FROM node:20-alpine
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
USER 1001
EXPOSE 3000
CMD ["node", "server.js"]For interpreted languages, the entire build runs under QEMU emulation for non-native platforms. This is slower but unavoidable when native extensions (e.g., bcrypt, sharp) must be compiled for the target architecture.
Extended Platform Matrix
- name: Build and push (all platforms)
uses: docker/build-push-action@v6
with:
context: .
platforms: |
linux/amd64
linux/arm64
linux/arm/v7
linux/arm/v6
linux/386
linux/ppc64le
linux/s390x
push: true
tags: user/myapp:latestNEVER include platforms your application does not support. Test on each platform before adding it to the matrix.
---
Registry Authentication Examples
Docker Hub with Personal Access Token
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}Create the token at https://hub.docker.com/settings/security. Select "Read & Write" scope for push access.
GHCR with GITHUB_TOKEN
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}Requires permissions: packages: write at the job or workflow level. No manual secret creation needed.
AWS ECR with OIDC (Recommended)
permissions:
id-token: write
contents: read
steps:
- 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: Log in to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2OIDC eliminates long-lived access keys. The IAM role must trust the GitHub OIDC provider and allow ecr:GetAuthorizationToken plus ecr:BatchCheckLayerAvailability, ecr:PutImage, etc.
AWS ECR with Access Keys (Legacy)
- name: Log in to Amazon ECR
uses: docker/login-action@v3
with:
registry: 123456789012.dkr.ecr.us-east-1.amazonaws.com
username: ${{ secrets.AWS_ACCESS_KEY_ID }}
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}ALWAYS prefer OIDC over static access keys. If using access keys, rotate them regularly and use the minimum required IAM permissions.
Azure ACR
- name: Log in to Azure ACR
uses: docker/login-action@v3
with:
registry: myregistry.azurecr.io
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}Google Artifact Registry
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123/locations/global/workloadIdentityPools/pool/providers/provider
service_account: ci@project.iam.gserviceaccount.com
- name: Log in to GAR
uses: docker/login-action@v3
with:
registry: us-docker.pkg.dev
username: oauth2accesstoken
password: ${{ steps.auth.outputs.access_token }}Self-Hosted Registry
- name: Log in to private registry
uses: docker/login-action@v3
with:
registry: registry.example.com
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}---
Cache Strategy Examples
GitHub Actions Cache (Recommended for GitHub CI)
- uses: docker/build-push-action@v6
with:
cache-from: type=gha
cache-to: type=gha,mode=maxCharacteristics:
- 10 GB total cache per repository.
- Branch-scoped: feature branch cache falls back to default branch.
- Fastest option in GitHub Actions (uses the same storage as
actions/cache). - ALWAYS use
mode=max—mode=minonly caches the final exported layers.
Registry Cache (Cross-CI, Cross-Branch)
- uses: docker/build-push-action@v6
with:
cache-from: type=registry,ref=user/myapp:buildcache
cache-to: type=registry,ref=user/myapp:buildcache,mode=maxCharacteristics:
- Shared across all branches, forks, and CI providers.
- Requires registry authentication.
- Adds push/pull time but avoids full rebuilds.
- Use a dedicated tag (
:buildcache) to avoid polluting image tags.
Multi-Source Cache Fallback
- uses: docker/build-push-action@v6
with:
cache-from: |
type=registry,ref=user/myapp:cache-${{ github.ref_name }}
type=registry,ref=user/myapp:cache-main
cache-to: type=registry,ref=user/myapp:cache-${{ github.ref_name }},mode=maxBuildKit tries cache sources in order. Feature branches get their own cache but fall back to the main branch cache when their own is empty.
Scoped Cache for Matrix Builds
strategy:
matrix:
service: [api, worker, web]
steps:
- uses: docker/build-push-action@v6
with:
context: .
file: docker/${{ matrix.service }}/Dockerfile
cache-from: type=gha,scope=build-${{ matrix.service }}
cache-to: type=gha,scope=build-${{ matrix.service }},mode=maxWithout scope, all matrix jobs share the same cache namespace and overwrite each other's entries. ALWAYS use scope when building multiple images in the same workflow.
Inline Cache (Simple, No External Storage)
- uses: docker/build-push-action@v6
with:
push: true
tags: user/myapp:latest
build-args: BUILDKIT_INLINE_CACHE=1
cache-from: type=registry,ref=user/myapp:latestInline cache embeds cache metadata directly in the pushed image. Simpler than dedicated cache images but only caches exported layers (mode=min equivalent).
Local Cache (Self-Hosted Runners)
- uses: docker/build-push-action@v6
with:
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
# Rotate cache to prevent unbounded growth
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cacheALWAYS rotate local cache (write to new dir, then swap) to prevent the cache directory from growing indefinitely. Each build appends new layers without removing old ones.
---
Build Argument Patterns
Version Injection
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
build-args: |
VERSION=${{ github.ref_name }}
COMMIT_SHA=${{ github.sha }}
BUILD_DATE=${{ github.event.head_commit.timestamp }}
push: true
tags: user/myapp:${{ github.ref_name }}# syntax=docker/dockerfile:1
ARG VERSION=dev
ARG COMMIT_SHA=unknown
ARG BUILD_DATE=unknown
FROM alpine:3.21
LABEL org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.revision="${COMMIT_SHA}" \
org.opencontainers.image.created="${BUILD_DATE}"
# ...Target Stage for Dev/Prod
# Development build
- uses: docker/build-push-action@v6
with:
context: .
target: development
load: true
tags: myapp:dev
# Production build
- uses: docker/build-push-action@v6
with:
context: .
target: production
push: true
tags: user/myapp:latest# syntax=docker/dockerfile:1
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
FROM base AS production
RUN npm ci --production
COPY . .
USER 1001
CMD ["node", "server.js"]---
Official Sources
- https://docs.docker.com/build/ci/github-actions/
- https://docs.docker.com/build/building/multi-platform/
- https://docs.docker.com/build/cache/backends/
- https://docs.docker.com/build/cache/backends/gha/
- https://github.com/docker/build-push-action
- https://github.com/docker/login-action
GitHub Actions Workflow Examples
Docker Hub: Build and Push
name: Docker Hub CI
on:
push:
branches: [main]
tags: ["v*.*.*"]
pull_request:
branches: [main]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: user/myapp
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
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=maxRequired Secrets for Docker Hub
| Secret/Variable | Where to Set | Value |
|---|---|---|
vars.DOCKERHUB_USERNAME | Repository Variables | Docker Hub username |
secrets.DOCKERHUB_TOKEN | Repository Secrets | Docker Hub Personal Access Token (NOT password) |
ALWAYS use a Personal Access Token with the minimum required scope (Read & Write for push). NEVER use your Docker Hub password.
---
GHCR (GitHub Container Registry): Build and Push
name: GHCR CI
on:
push:
branches: [main]
tags: ["v*.*.*"]
pull_request:
branches: [main]
permissions:
contents: read
packages: write
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
if: github.event_name != 'pull_request'
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 }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=ref,event=branch
type=sha,prefix=sha-
- 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=maxGHCR uses GITHUB_TOKEN which is automatically available. No manual secret creation needed. ALWAYS set permissions: packages: write at the job or workflow level.
---
Multi-Registry Push (Docker Hub + GHCR)
name: Multi-Registry CI
on:
push:
branches: [main]
tags: ["v*.*.*"]
permissions:
contents: read
packages: write
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- 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: |
user/myapp
ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=ref,event=branch
type=sha,prefix=sha-
- 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 }}
cache-from: type=gha
cache-to: type=gha,mode=maxWhen pushing to multiple registries, list ALL image names in the metadata-action images input. The action generates tags for each registry automatically.
---
AWS ECR: Build and Push
name: AWS ECR CI
on:
push:
branches: [main]
tags: ["v*.*.*"]
permissions:
id-token: write
contents: read
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActions
aws-region: us-east-1
- name: Log in to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.ecr-login.outputs.registry }}/myapp
tags: |
type=semver,pattern={{version}}
type=ref,event=branch
type=sha,prefix=sha-
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=maxALWAYS prefer OIDC authentication (role-to-assume) over static access keys for AWS. OIDC provides short-lived credentials and eliminates the need to store long-lived secrets.
---
Build Matrix: Multiple Dockerfiles
name: Matrix Build
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- dockerfile: Dockerfile
image: user/myapp
platforms: linux/amd64,linux/arm64
- dockerfile: Dockerfile.worker
image: user/myapp-worker
platforms: linux/amd64
- dockerfile: Dockerfile.migrations
image: user/myapp-migrations
platforms: linux/amd64
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
if: contains(matrix.platforms, 'arm')
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ matrix.image }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile }}
platforms: ${{ matrix.platforms }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.dockerfile }}
cache-to: type=gha,scope=${{ matrix.dockerfile }},mode=maxALWAYS use the scope parameter on type=gha cache when building multiple images. Without scope, cache entries from different Dockerfiles overwrite each other.
---
Build with Secrets
name: Build with Secrets
on:
push:
branches: [main]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
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@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
secrets: |
"npm_token=${{ secrets.NPM_TOKEN }}"
"github_token=${{ secrets.GITHUB_TOKEN }}"
cache-from: type=gha
cache-to: type=gha,mode=maxIn the Dockerfile, access secrets via mount:
# syntax=docker/dockerfile:1
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) \
npm ci --registry https://npm.pkg.github.com
COPY . .
RUN npm run buildNEVER pass secrets via build-args. They appear in docker history and are cached in image layers.
---
Docker Scout Integration
name: Build and Scan
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and load locally
uses: docker/build-push-action@v6
with:
context: .
load: true
tags: myapp:local
- name: Docker Scout CVE scan
uses: docker/scout-action@v1
with:
command: cves
image: local://myapp:local
only-severities: critical,high
exit-code: true
- name: Docker Scout recommendations
if: always()
uses: docker/scout-action@v1
with:
command: recommendations
image: local://myapp:local
- name: Push if scan passes
if: success()
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: user/myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=maxALWAYS build and scan locally first, then push only if the scan passes. This prevents vulnerable images from reaching the registry.
---
Scheduled Rebuild for Base Image Updates
name: Scheduled Rebuild
on:
schedule:
- cron: "0 4 * * 1" # Every Monday at 04:00 UTC
workflow_dispatch:
jobs:
rebuild:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push (fresh base)
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: user/myapp:latest
build-args: |
BUILDKIT_INLINE_CACHE=1
no-cache: true
pull: trueUse no-cache: true and pull: true together to ensure the scheduled rebuild picks up all base image security patches. ALWAYS combine scheduled rebuilds with vulnerability scanning.
---
Provenance and SBOM Attestations
- name: Build and push with attestations
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: mode=max
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=maxprovenance: mode=maxgenerates SLSA provenance attestations with full build metadata.sbom: truegenerates a Software Bill of Materials attached to the image.- Attestations are stored as OCI image manifests alongside the image.
- ALWAYS enable provenance and SBOM for production images to support supply chain security verification.
---
Official Sources
- https://docs.docker.com/build/ci/github-actions/
- https://github.com/docker/build-push-action
- https://github.com/docker/metadata-action
- https://github.com/docker/login-action
- https://github.com/docker/setup-buildx-action
- https://github.com/docker/setup-qemu-action
- https://github.com/docker/scout-action