Multi‑Stage Docker Builds & SBOM: Shrink Images and Meet Security Compliance
This guide shows how to dramatically reduce container image size using Docker multi‑stage builds, choose minimal base images, automatically generate SPDX/CycloneDX SBOMs, sign images with Cosign, and integrate the whole process into CI/CD pipelines for secure, lightweight deployments.
Overview
The article starts with a real‑world problem: a Go micro‑service Docker image built from ubuntu:latest grew to 1.2 GB, causing slow pulls, long deployments, and a large attack surface. It then links image bloat to the rising importance of software supply‑chain security (e.g., Log4j, SolarWinds) and the US Executive Order 14028 that mandates SBOM generation.
Technical Features
Multi‑stage builds : Separate build and runtime stages with FROM golang:1.22‑alpine AS builder and FROM gcr.io/distroless/static-debian12:nonroot (or scratch).
Minimal base images : Use scratch, distroless, or alpine to keep only required libraries.
SBOM generation : syft and trivy produce SPDX or CycloneDX documents, which can be attached to the image.
Image signing : cosign signs the image and attaches the SBOM for supply‑chain integrity.
CI/CD integration : GitHub Actions workflow demonstrates building, tagging, SBOM creation, signing, and vulnerability scanning.
Step‑by‑Step Process
Analyze the existing image with docker images, docker history, or dive to locate large layers (build tools, dev dependencies, caches, docs).
Enable BuildKit ( export DOCKER_BUILDKIT=1) for efficient caching and parallelism.
Create a Dockerfile with multiple FROM statements, copy only go.mod, go.sum, and compiled binaries, and strip debug symbols ( -ldflags="-w -s").
Choose a minimal runtime image ( scratch for static binaries, distroless for glibc‑based languages, or alpine when a shell is required).
Build the image: DOCKER_BUILDKIT=1 docker build -t myapp:1.0 . Generate an SBOM: syft myapp:1.0 -o spdx-json > sbom-spdx.json Scan for vulnerabilities: trivy image --severity HIGH,CRITICAL myapp:1.0 Sign the image and attach the SBOM:
cosign sign --yes myregistry/myapp@sha256:…
cosign attach sbom --sbom sbom-spdx.json myregistry/myapp@sha256:…Integrate all steps into a CI workflow (see the provided .github/workflows/docker-build.yml example).
Use Cases
Go micro‑service : Reduced image size from 1.1 GB to ~12 MB, pull time from 45 s to 2 s, and eliminated 127 high‑severity CVEs.
React front‑end : Switched from a 850 MB Node image to a 25 MB Nginx‑alpine image, halving build time and cutting high‑severity vulnerabilities from 45 to 2.
Best Practices & Pitfalls
Use a .dockerignore to exclude source control files, docs, and caches.
Combine related RUN commands to reduce layer count.
Pin base‑image versions (avoid :latest).
When using scratch or distroless, ensure the binary is fully static and copy required certificates or timezone data.
Validate SBOM format (SPDX or CycloneDX) and verify it matches the signed image.
Troubleshooting & Monitoring
Common issues include missing libraries in scratch images, container exit codes, DNS failures, and permission problems when copying files across stages. The article provides diagnostic commands ( docker logs, docker inspect, dive) and remediation steps.
Performance metrics (image size, build time, SBOM component count, high‑severity vulnerabilities) can be tracked with simple time measurements, docker images --format, and BuildKit metadata files. Automated CI checks can enforce size limits and vulnerability thresholds.
Conclusion
By combining multi‑stage Docker builds, minimal base images, automated SBOM generation, and Cosign signing, teams can achieve up to 99 % image size reduction, faster deployments, and compliance with emerging supply‑chain security regulations.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
