0%
DevOps

Docker Best Practices for Efficient and Secure Builds

Docker Documentation Team Docker Documentation Team
5 min read

Master Docker with best practices like multi-stage builds, minimal base images, and CI testing to optimize your container workflows.

Docker has revolutionized how we build, ship, and run applications, but maximizing its potential requires following best practices. Whether you’re a seasoned DevOps engineer or a developer diving into containers, optimizing your Dockerfiles and workflows can save time, reduce vulnerabilities, and improve performance. Let’s explore the key strategies from the official Docker documentation to level up your container game!

Multi-stage build diagram for Docker

Core Best Practices

Use Multi-Stage Builds for Lean Images

Multi-stage builds are a game-changer for reducing image size and separating build and runtime environments. By splitting your Dockerfile into stages, you ensure the final image contains only what’s needed to run your app, ditching bulky build tools. For example:

# Build stage
FROM golang:1.20 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

# Runtime stage
FROM alpine:3.21
WORKDIR /app
COPY --from=builder /app/myapp .
CMD ["./myapp"]

This approach also enables parallel build steps, boosting efficiency. Check out Multi-stage builds for more details.

Create Reusable Stages

For projects with shared components, define a reusable base stage. Docker builds it once, saving memory and speeding up derivative image loads. It’s a “don’t repeat yourself” win for maintenance too!

Choose the Right Base Image

Your base image is the foundation of security and performance. Opt for trusted, minimal images:

  • Docker Official Images: Curated, well-documented, and regularly updated—perfect starting points.
  • Verified Publisher Images: High-quality, Docker-verified images from partner organizations.
  • Docker-Sponsored Open Source: Maintained by sponsored open-source projects.

Docker Hub Official and Verified Publisher badges

Use lightweight images like Alpine (under 6 MB) and consider separate build (e.g., with compilers) and production (slimmer) images to minimize vulnerabilities.

Rebuild Images Often

Docker images are immutable snapshots, so rebuild frequently to incorporate updated dependencies and security patches. Use --no-cache to force fresh downloads:

docker build --no-cache -t my-image:latest .

Pin base image versions (e.g., alpine:3.21@sha256:...) for consistency, but leverage tools like Docker Scout for automated updates with audit trails.

Exclude Unnecessary Files with .dockerignore

A .dockerignore file keeps irrelevant files out of your build context, speeding up builds. Example:

*.md
node_modules

See Dockerignore file for setup guidance.

Build Ephemeral Containers

Design containers to be stateless and disposable. Stop, destroy, and rebuild them with minimal setup, aligning with the Twelve-Factor App methodology’s process focus.

Avoid Unnecessary Packages

Skip installing extras like text editors in a database image. Less complexity means smaller, faster, and safer images.

Decouple Applications

Keep containers single-purpose (e.g., web app, database, cache) for easier scaling and reuse. Use Docker networks for communication if dependencies exist.

Optimizing Dockerfile Instructions

Sort Multi-Line Arguments

Organize multi-line RUN commands alphanumerically for clarity:

RUN apt-get update && apt-get install -y --no-install-recommends \
  bzr \
  git \
  mercurial \
  && rm -rf /var/lib/apt/lists/*

This reduces duplication and simplifies PR reviews.

Leverage Build Cache

Docker reuses cached layers to speed up builds. Understand cache invalidation (e.g., changing FROM triggers a rebuild) to optimize—see Docker build cache for tips.

Pin Base Image Versions

Tags like alpine:3.21 can shift over time. Pin to digests (e.g., alpine:3.21@sha256:...) for reproducibility, though Docker Scout can automate updates.

Build and Test in CI

Automate builds and tests with CI/CD (e.g., GitHub Actions) on every code change or pull request.

Dockerfile Instruction Best Practices

FROM

Use official images like Alpine for minimal size. Example:

FROM alpine:3.21

LABEL

Add metadata for organization:

LABEL com.example.version="1.0.0" com.example.release-date="2025-07-25"

RUN

Combine apt-get update with install to avoid cache issues:

RUN apt-get update && apt-get install -y --no-install-recommends \
  curl \
  && rm -rf /var/lib/apt/lists/*

Use set -o pipefail for piped commands:

RUN set -o pipefail && wget -O - https://example.com | wc -l > /number

CMD

Set the main executable:

CMD ["nginx", "-g", "daemon off;"]

EXPOSE

Indicate ports:

EXPOSE 80

ENV

Update PATH or set versions:

ENV PATH=/usr/local/bin:$PATH PG_VERSION=15.3

ADD or COPY

Prefer COPY for files, ADD for remote artifacts with checksums:

ADD --checksum=sha256:... https://example.com/file.tar.gz /app/

ENTRYPOINT

Define the default command:

ENTRYPOINT ["python3"]
CMD ["-m", "http.server"]

VOLUME

Expose mutable data:

VOLUME /data

USER

Switch to non-root:

RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser

WORKDIR

Use absolute paths:

WORKDIR /app

ONBUILD

Add instructions for child images:

ONBUILD COPY . /app

Why It Matters

Following these practices ensures efficient, secure, and maintainable Docker images. Pair them with tools like Docker Scout and the Docker VS Code Extension for a smoother experience. Start optimizing your builds today!

More in DevOps