iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Docker in CI/CD

Docker in CI/CD: building images, multi-stage builds, layer caching, registries, and image promotion.

Docker — CI/CD

EXAMPLE
# ===== Multi-stage Dockerfile =====
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER nonroot
CMD ["dist/server.js"]

# Final image: tiny, no shell, non-root.

# ===== GitHub Actions =====
name: docker
on:
  push: { branches: [main] }
jobs:
  build:
    runs-on: ubuntu-latest
    permissions: { contents: read, packages: write }
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:${{ github.sha }}
            ghcr.io/${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64

# ===== Layer caching =====
# Order Dockerfile lines from LEAST to MOST frequently changing:
# 1. FROM (rarely)
# 2. System deps (rarely)
# 3. package.json (sometimes)
# 4. npm ci (when package.json changes)
# 5. App code (often)
# This maximises cache hits during builds.

# ===== Registries =====
# Docker Hub        public + paid private
# GitHub GHCR       free for public + tied to repo perms
# AWS ECR           AWS-native; IAM integration
# Google GAR        GCP-native
# Quay (Red Hat)    enterprise

# ===== Image promotion =====
# Build once; tag through environments:
docker tag shop:sha-abc shop:dev
docker tag shop:sha-abc shop:staging
docker tag shop:sha-abc shop:prod

# Never rebuild for prod from main — same artifact through all environments.

# ===== Security =====
# - Trivy / Grype scan in CI; fail on HIGH+
# - Sign images with cosign + Sigstore
# - Pin base by digest in production
# - Distroless / scratch / alpine where possible
# - Non-root user
# - SBOM (Syft) attached to each image

# ===== Image tags =====
# Avoid 'latest' in production. Recommended tag scheme:
# - <git-sha>           immutable build artifact
# - <semver>            for releases (v1.2.3)
# - <branch-name>       for dev / preview
# - <env>-latest        moving pointer per environment

# ===== Patterns =====
# - Multi-stage builds; tiny runtime image
# - Layer cache friendly Dockerfile ordering
# - Build once, promote artifact through environments
# - Sign + scan + SBOM in pipeline
# - Pin base by digest in prod

# ===== Pitfalls =====
# - 'latest' tag in prod -> non-reproducible deploys
# - Rebuilding for each environment (drift)
# - Root user inside container
# - Huge final image (forgot to use multi-stage)

Why it matters

Docker in CI: multi-stage build, layer cache, tagged push to a registry, scan + sign in the pipeline. Promote ONE artifact through environments by tag; never rebuild for prod. Pin base by digest, non-root user, distroless runtime — the production image gets safe + tiny.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
# GitHub Actions snippet
- uses: docker/build-push-action@v5
    with:
        push: true
        tags: ghcr.io/user/my-api:${{ github.sha }}
Try it Yourself »

Discussion

Loading…