Skip to content

Container Build Strategies — Complete Guide to Optimized Docker Images

DodaTech Updated 2026-06-23 7 min read

In this tutorial, you'll learn about Container Build Strategies. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Container build strategies define how application source code is transformed into optimized Docker images using multi-stage builds, layer Caching, minimal base images, and BuildKit features to produce secure, small, and fast-deploying artifacts.

What You'll Learn

You'll learn how to write multi-stage Dockerfiles, leverage layer Caching for fast rebuilds, choose minimal and distroless base images, use BuildKit's advanced features, and integrate container builds into CI/CD pipelines for production deployments.

Why Container Build Strategies Matter

Container images are the primary deployment artifact for modern applications. Bloated images slow deployments, increase storage costs, and expand the attack surface. A well-optimized build pipeline reduces image size by 10-50x and improves security by eliminating unnecessary packages.

Real-World Use

DodaZIP's content-processing Microservices reduced image size from 1.2 GB to 47 MB by switching from Ubuntu base images to distroless and implementing multi-stage builds with BuildKit Caching, reducing deployment time from 45 seconds to under 3 seconds.

Prerequisites

  • Docker installed and basic familiarity
  • Understanding of Linux package management
  • Basic CI/CD pipeline knowledge

Step 1: Multi-Stage Builds

Multi-stage builds separate the build environment from the runtime environment, keeping only the compiled binary in the final image.

# Stage 1: Build environment
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app/server

# Stage 2: Runtime environment
FROM alpine:3.20
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /app/server /server
EXPOSE 8080
USER nobody
ENTRYPOINT ["/server"]
docker build -t myapp:latest .
docker images myapp
# REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
# myapp        latest    a1b2c3d4e5f6   10 seconds ago   18.7 MB

Expected output: The final image contains only the Go binary plus Alpine's minimal runtime (ca-certificates, tzdata). The Go compiler and source code are discarded after the build stage.

Step 2: Layer Caching Optimization

Docker builds each instruction as a layer. Ordering instructions from least to most frequently changing maximizes cache reuse.

# Optimized layer ordering for caching
FROM node:20-alpine AS builder
WORKDIR /app

# Layer 1: Install dependencies (rarely changes)
COPY package.json package-lock.json ./
RUN npm ci --only=production

# Layer 2: Build tools (rarely changes)
COPY .eslintrc.js tsconfig.json ./
COPY src/ ./src/

# Layer 3: Build (changes with source)
RUN npm run build

# Production stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
USER node
CMD ["node", "dist/index.js"]
# Measure cache effectiveness
docker build --cache-from myapp:latest -t myapp:latest .

# First build: 120 seconds
# Source-only change: 5 seconds (cached dependency layer)
# Dependency change: 45 seconds (invalidates subsequent layers)

Expected behavior: When only source files change, the npm ci layer is pulled from cache. When package.json changes, that layer and all subsequent layers are rebuilt.

Step 3: Distroless and Scratch Images

Distroless images contain only the application and its runtime dependencies — no shell, package manager, or system utilities.

# Rust application with distroless image
FROM rust:1.77 AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release
COPY src/ src/
RUN touch src/main.rs && cargo build --release

# Use distroless CC image for glibc-based binaries
FROM gcr.io/distroless/cc-debian12 AS runtime
COPY --from=builder /app/target/release/myservice /myservice
EXPOSE 8080
ENTRYPOINT ["/myservice"]
# Scratch image for fully static Go binaries
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w -extldflags=-static" .

# Scratch has nothing — just the binary
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
# Compare image sizes
docker images | grep -E "(myapp|myservice)"
# myapp         golang-multistage   18.7 MB
# myapp         scratch             8.4 MB
# myservice     distroless          12.1 MB
# myservice     ubuntu              185 MB

Expected output: Scratch images are the smallest possible (binary only) but have no shell for debugging. Distroless images include libc and ca-certificates for C-based applications without a full OS.

Step 4: BuildKit Advanced Features

BuildKit provides parallel builds, secrets handling, and cache mounts that significantly improve build performance.

# syntax=docker/dockerfile:1
FROM python:3.12-slim AS builder

# Cache mount for pip packages
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install --upgrade pip

WORKDIR /app
COPY requirements.txt .

# Bind mount for credentials (not copied into image)
RUN --mount=type=bind,source=requirements.txt,target=requirements.txt \
    --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

COPY . .

# Use Docker secrets for private packages
RUN --mount=type=secret,id=github_token \
    GITHUB_TOKEN=$(cat /run/secrets/github_token) && \
    pip install git+https://${GITHUB_TOKEN}@github.com/org/private-package.git
# Build with BuildKit
DOCKER_BUILDKIT=1 docker build \
    --secret id=github_token,src=./github_token.txt \
    -t myapp:latest .

# Use cache from registry for CI performance
docker buildx build \
    --cache-from=type=registry,ref=myapp:buildcache \
    --cache-to=type=registry,ref=myapp:buildcache,mode=max \
    -t myapp:latest .

Expected behavior: BuildKit's cache mounts persist pip packages across builds, preventing repeat downloads. Secrets are mounted temporarily and never stored in image layers.

Architecture

flowchart LR
    subgraph "Build Pipeline"
        SRC[Source Code]
        DEP[Dependencies]
        Builder[Builder Stage]
        COMPILE[Compilation]
    end
    subgraph "Image Assembly"
        BASE[Base Image]
        ARTIFACT[Compiled Artifact]
        RUNTIME[Runtimes
libc, certs] IMAGE[Final Image] end subgraph "Deployment" REGISTRY[Container Registry] PROD[Production] end SRC --> Builder DEP --> Builder Builder --> COMPILE COMPILE --> ARTIFACT BASE --> IMAGE ARTIFACT --> IMAGE RUNTIME --> IMAGE IMAGE --> REGISTRY REGISTRY --> PROD

Common Errors

1. COPY failed: file not found in build context The file referenced in COPY or ADD does not exist in the build context. Ensure all required files are in the directory passed to docker build (or use .dockerignore to exclude unnecessary files).

2. Encountered 4 file(s) that should have been pointers, but aren't This Occurs when using BuildKit with incompatible storage drivers. Set DOCKER_BUILDKIT=0 temporarily or upgrade to a supported driver (overlay2 recommended).

3. /bin/sh: ./binary: not found when using scratch scratch has no shell and no libc. Either compile with CGO_ENABLED=0 for a fully static binary, or use gcr.io/distroless/cc which provides glibc.

4. Layer cache invalidated on every build If COPY . . copies frequently changing files, all subsequent layers rebuild. Move dependency installation before source copy, and use .dockerignore to exclude non-essential files.

5. Image vulnerability scan reports critical CVEs Base images accumulate vulnerabilities over time. Use distroless or slim images, scan with Docker Scout or Trivy, and rebuild regularly to pull patched base images.

Practice Questions

1. What is the primary benefit of multi-stage builds? Multi-stage builds separate the build environment from the runtime, allowing the final image to contain only the compiled binary and minimal runtime dependencies, drastically reducing image size and attack surface.

2. How does layer Caching improve build performance? Docker caches each instruction's result. If the instruction and its context haven't changed, Docker reuses the cached layer. Ordering instructions from stable to frequently changing maximizes cache hits.

3. What is the difference between alpine and distroless base images? Alpine is a minimal Linux distribution with a shell and package manager. Distroless images contain only the application and runtime libraries — no shell, no package manager, reducing the attack surface further.

4. Challenge: Build a production-ready Dockerfile for a Python web application Create a multi-stage Dockerfile for a Flask/FastAPI application. Use pip cache mounts, distroless base for the runtime stage, and implement health checks. Target an image size under 150 MB.

5. Challenge: Multi-architecture image

docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t myapp:multiarch --push .

Create a buildx command that builds and pushes a multi-architecture manifest for the same application across three platforms.

FAQ

What is the difference between COPY and ADD in Dockerfiles?

COPY copies files from the build context into the image. ADD supports additional features like URL downloads and automatic tar extraction. Use COPY unless you specifically need ADD's extra capabilities.

How do I reduce image size further?

Use smaller base images (scratch, distroless, alpine), remove unnecessary packages, run docker image prune, and use --squash (experimental) to flatten layers.

What is Docker BuildKit and why use it?

BuildKit is Docker's next-generation build backend. It provides parallel builds, intelligent Caching, secret mounts, SSH agent forwarding, and better performance compared to the legacy Builder.

Next Steps

  • Learn how CI/CD pipelines automate container builds with GitHub Actions
  • Explore Kubernetes deployment strategies for containerized applications
  • Study the Docker registry tutorial for managing private image repositories
  • Review the cross-compilation guide for multi-architecture container images

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro