Stage 5 · Platform
Build, Version & Release
Building Containers in CI
Using BuildKit, docker buildx, multi-stage Dockerfiles, and multi-architecture image builds.
Dockerfile Best Practices
A well-written Dockerfile produces small, secure, and reproducible images. Multi-stage builds separate the build environment from the runtime environment, reducing image size and attack surface.
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:20-alpine AS production
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
CMD ["node", "dist/index.js"]The builder stage installs dependencies and compiles the application. The production stage copies only the built output and runtime dependencies. The non-root user, health check, and minimal base image follow security best practices.
BuildKit
BuildKit is the modern Docker build engine. It provides parallel build stages, efficient caching, and secret mounting. Enable it with DOCKER_BUILDKIT=1 or by using Docker 23.0+ where it is the default.
# syntax=docker/dockerfile:1
# Mount secrets without persisting them in layers
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci --only=production
# Cache mount for package manager
RUN --mount=type=cache,target=/root/.npm \
npm ci --only=production
# Build arg with default
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
# Multi-platform build
FROM --platform=$BUILDPLATFORM node:20-alpine AS builderBuildKit's --mount=type=secret prevents credentials from being cached in image layers. The cache mount speeds up repeated builds by preserving the npm cache. Build-time ARGs allow configuration without modifying the Dockerfile.
Docker Buildx
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login 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/myorg/myapp:${{ github.sha }}
ghcr.io/myorg/myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
NODE_ENV=production
BUILD_DATE=${{ github.event.head_commit.timestamp }}
platforms: linux/amd64,linux/arm64Docker Buildx replaces the standard docker build command. It supports multi-platform builds, advanced caching, and build arguments. The GHA cache backend stores layers in GitHub Actions cache for fast subsequent builds.
Multi-Architecture Builds
jobs:
build:
runs-on: ubuntu-latest
steps:
- 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: Build multi-arch image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ghcr.io/myorg/myapp:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Create manifest
run: |
docker manifest create ghcr.io/myorg/myapp:latest \
ghcr.io/myorg/myapp:linux-amd64 \
ghcr.io/myorg/myapp:linux-arm64
docker manifest push ghcr.io/myorg/myapp:latestQEMU emulates different CPU architectures, allowing ARM builds on x86 runners. This is slow but works for occasional builds. For frequent ARM builds, use native ARM runners for better performance.
Docker Layer Caching
- GHA cache backend — stores layers in GitHub Actions cache. Fast and integrated. Use for most projects.
- Registry cache — pushes layers to the registry as a separate image. Good for cross-CI systems.
- Local cache — caches on the runner filesystem. Fast but only works with self-hosted runners.
- Inline cache — embeds cache metadata in the image. Works with any registry but increases image size.
Image Optimization
Smaller images pull faster, start faster, and have a smaller attack surface. Use alpine or distroless base images, multi-stage builds, and --no-install-recommends for apt. Target images under 100MB for production.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.