Skip to main content

Dockerfile Best Practices 2025: Building Lightweight, Secure, and Fast Images

The way you write your Dockerfile can result in a 10x difference in image size, 20x difference in build time, and 5x difference in vulnerability count. Evolving from "it works" to "production-ready" Dockerfiles is an essential skill for DevOps engineers in 2025.

At Kubo, we manage numerous container workloads on Kubernetes where Dockerfile quality directly impacts cluster-wide performance and security. This article systematically covers Dockerfile best practices based on the latest 2025 insights.

1. Base Image Selection: The Foundation of Lightweight Images

Base image selection has the greatest impact on final image size and security. The Docker official documentation recommends images from trusted sources.

Base Image Comparison

Base ImageSizeCVEs (approx.)ShellUse Case
ubuntu:24.04~77MB30-50bashGeneral (not recommended)
debian:bookworm-slim~52MB20-40bashLightweight Debian
alpine:3.21~5MB5-10sh (busybox)Lightweight + debuggable
distroless/static~2MB0-2NoneGo/Rust static binaries
distroless/base~20MB2-5Noneglibc-dependent apps
scratch0MB0NoneFully custom

Google's distroless images contain only your application and its runtime dependencies, with no shell or package manager, minimizing the attack surface.

dockerfile
# Recommended: Choose minimal images for your use case
# Go applications
FROM gcr.io/distroless/static-debian12

# Node.js applications
FROM gcr.io/distroless/nodejs20-debian12

# Python applications
FROM python:3.12-slim

Version pinning is essential for reproducibility:

dockerfile
# Bad: Tag may change
FROM node:20

# Good: Specific version
FROM node:20.18-alpine3.21

# Best: Pinned by digest
FROM node:20.18-alpine3.21@sha256:abc123...

Captain.AI analyzes your project's language and framework to automatically recommend optimal base images.

2. Layer Structure Optimization

Docker uses layer caching to speed up builds. Optimizing instruction order maximizes cache hit rates.

Instruction Order: Least Frequently Changed First

dockerfile
# 1. Base image (rarely changes)
FROM node:20-alpine AS builder

# 2. System dependency installation (infrequent changes)
RUN apk add --no-cache python3 make g++

# 3. Copy dependency definitions (changes when packages added)
WORKDIR /app
COPY package.json package-lock.json ./

# 4. Install dependencies (re-runs when packages added)
RUN npm ci

# 5. Copy source code (frequent changes)
COPY . .

# 6. Build (re-runs on source code changes)
RUN npm run build

With this order, source code changes trigger only steps 5-6, while dependency installation (potentially minutes long) remains cached. ByteScrum's best practices article puts it aptly: "Wrong order = 20-minute rebuilds; correct order = 20-second rebuilds."

Consolidating RUN Instructions

dockerfile
# Bad: Creates 3 layers, intermediate files persist
RUN apt-get update
RUN apt-get install -y curl wget
RUN rm -rf /var/lib/apt/lists/*

# Good: Single layer, cache cleaned
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      curl \
      wget \
    && rm -rf /var/lib/apt/lists/*

3. Essential Security Hardening

According to Sysdig's research, 76% of production containers run as root — a critical security risk.

Run as Non-Root User

dockerfile
# Create application user
RUN addgroup -g 10001 -S appgroup && \
    adduser -u 10001 -S appuser -G appgroup

# Set ownership of required directories
COPY --chown=appuser:appgroup . /app

# Switch to non-root user
USER appuser

Using UIDs above 10000 avoids ID collisions with host OS system users.

Secret Management

dockerfile
# NEVER do this: Secrets persist in image layers
COPY .env /app/.env
ENV API_KEY=sk-secret-key

# Correct: BuildKit secret mounts
RUN --mount=type=secret,id=api_key \
    export API_KEY=$(cat /run/secrets/api_key) && \
    ./setup.sh

BuildKit secret mounts make secrets available only during build without persisting them in image layers.

Leveraging .dockerignore

gitignore
# .dockerignore
.git
.gitignore
node_modules
.env
.env.*
*.md
docker-compose*.yml
Dockerfile*
.DS_Store
__pycache__
*.pyc
coverage/
.pytest_cache/

.dockerignore reduces build context size and prevents unintended files — especially secrets — from being included in images. GeeksforGeeks' Dockerfile guide highlights this as an essential practice.

Kubo's security policies recommend non-root execution and .dockerignore usage as standards.

4. Leveraging BuildKit

BuildKit is Docker's next-generation build engine, offering parallel builds, improved caching, and security features.

Enabling BuildKit

bash
# Enable via environment variable
export DOCKER_BUILDKIT=1

# Use docker buildx (recommended)
docker buildx build -t myapp:latest .

Cache Mounts

Share dependency package caches across builds to avoid reinstallation:

dockerfile
# Persist pip cache
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

# Persist npm cache
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# Persist Go module cache
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go build -o /app/server .

# Persist apt cache
RUN --mount=type=cache,target=/var/cache/apt \
    --mount=type=cache,target=/var/lib/apt \
    apt-get update && apt-get install -y curl

Heredoc Syntax

dockerfile
# Traditional approach (cumbersome escaping)
RUN echo "server {" > /etc/nginx/conf.d/default.conf && \
    echo "  listen 80;" >> /etc/nginx/conf.d/default.conf && \
    echo "  location / { proxy_pass http://app:8080; }" >> /etc/nginx/conf.d/default.conf && \
    echo "}" >> /etc/nginx/conf.d/default.conf

# BuildKit heredoc (readable)
COPY <<EOF /etc/nginx/conf.d/default.conf
server {
    listen 80;
    location / {
        proxy_pass http://app:8080;
    }
}
EOF

5. Image Size Reduction Techniques

Prefer COPY Over ADD

dockerfile
# Avoid: ADD downloads remote URLs and auto-extracts tarballs
ADD https://example.com/app.tar.gz /app/

# Preferred: COPY is explicit local file copy only
COPY app/ /app/

ADD can cause unexpected behavior — always use COPY for local files.

Eliminate Unnecessary Packages

dockerfile
# Exclude recommended packages with --no-install-recommends
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      ca-certificates \
      curl \
    && rm -rf /var/lib/apt/lists/*

# On Alpine, disable apk cache with --no-cache
RUN apk add --no-cache curl

Multi-Stage Build Patterns

dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --production

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER node
CMD ["node", "dist/index.js"]

The DevOps Training Institute optimization guide reports that combining these techniques achieves over 80% size reduction.

Regular Image Rebuilds

Rebuild production images at least monthly to apply the latest base image security patches. Automate this with Dependabot or Renovate as Docker officially recommends.

6. Defining Health Checks

dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD curl -f http://localhost:8080/healthz || exit 1

The HEALTHCHECK instruction allows Docker to determine container health. While Kubernetes replaces this with liveness/readiness Probes, it remains valuable in Docker Compose environments.

Summary: 2025 Dockerfile Checklist

The definitive checklist for production-quality Dockerfiles:

  • select minimal base images (alpine - distroless - scratch)
  • Pin versions for reproducibility
  • Place infrequently changing instructions first (cache optimization)
  • Consolidate RUN instructions and clean caches
  • Run as non-root user (UID 10000+)
  • Minimize build context with .dockerignore
  • Manage secrets via BuildKit mounts
  • Separate production and development with multi-stage builds
  • Prefer COPY over ADD
  • Define HEALTHCHECK
  • Schedule regular rebuild cycles

Kubo efficiently runs container images that follow these best practices on Kubernetes clusters. With Captain.AI, automated Dockerfile reviews and optimization suggestions elevate your entire team's Dockerfile quality.

For Dockerfile optimization and container operations consulting, please contact us.

← Back to all posts