DevOpsLesson
DevOpsLesson

Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.

Stay Updated

Get notified about new tutorials and features.

Tutorials

  • What is DevOps?
  • Docker Tutorial
  • Terraform Tutorial
  • CI/CD Pipeline
  • All Tutorials

Roadmaps

  • DevOps Engineer
  • Cloud Engineer
  • SRE Path
  • All Roadmaps

Company

  • About Us
  • Blog
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 DevOpsLesson. All rights reserved.

DOCKERKUBERNETESTERRAFORMAWSCI/CDLINUXGITDEVOPS ROADMAPCLOUD ROADMAPSRE ROADMAPGIT CHEATSHEETDOCKER CHEATSHEETK8S CHEATSHEETTF CHEATSHEETLINUX CHEATSHEETDOCKERFILE LINTERYAML VALIDATORCRON PARSERREGEX TESTER

Docker Tutorial

Introduction to docker
Why Use Docker?
Docker vs Virtual Machines
Installing Docker
Key Docker Concepts
Docker Images
Docker Containers
Writing Dockerfiles
Docker Volumes

Best Practices

PreviousPrev
Next

The definitive checklist for production-grade Dockerfiles - security, caching, size, linting, and the habits that separate good images from great ones.

Best Practices

A Dockerfile that works is not the same as a Dockerfile that's production-ready. These practices address security, performance, maintainability, and reliability - the four axes of a well-crafted image.


Security

1. Never run as root

# Default - root in production
FROM node:20-alpine
CMD ["node", "server.js"]

# Non-root user
FROM node:20-alpine
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["node", "server.js"]

If an attacker exploits your app inside a root container, they have root on the host. A non-root user contains the blast radius.

2. Use read-only filesystem where possible

docker run --read-only myapp

Or mark specific write paths in the Dockerfile:

VOLUME /tmp
VOLUME /app/uploads
# Everything else is read-only at runtime with --read-only

3. Never bake secrets into images

# Secret in ENV - visible in docker inspect and image history
ENV DB_PASSWORD=supersecret

# Secret in ARG - visible in docker history
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc

#  BuildKit secret mount - never stored in any layer
RUN --mount=type=secret,id=npm_token \
    echo "//registry.npmjs.org/:_authToken=$(cat /run/secrets/npm_token)" >> .npmrc && \
    npm install && \
    rm .npmrc

4. Pin base image versions

# Unpredictable
FROM node:latest

#  Specific version - reproducible
FROM node:20.11.0-alpine3.19

#  Most secure - digest pinning
FROM node:20-alpine@sha256:bf77dc26e48ea95fca9d1aceb5acfa69d2e546b765ec2abfb502975f1a2d4def

5. Scan images regularly

# Docker Scout (built-in)
docker scout cves myapp:latest

# Trivy (open source)
trivy image myapp:latest

# Include in CI pipeline
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest

Build Performance (Caching)

6. Order by change frequency - least to most

FROM node:20-alpine          # Never changes
WORKDIR /app                 # Rarely changes
RUN apk add --no-cache curl  # Rarely changes

COPY package*.json ./        # Changes when deps update
RUN npm ci                   # Cached unless package.json changes

COPY . .                     # Changes every commit
CMD ["node", "server.js"]

7. Separate dependency install from source copy

This is the single highest-impact caching optimisation for most projects:

#  npm ci only reruns when package.json changes - not on every code edit
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .

8. Use .dockerignore

node_modules/
.git/
.env
*.log
coverage/
dist/
.DS_Store

Without it, COPY . . sends gigabytes to the daemon and busts the cache when irrelevant files change.


Image Size

9. Use the smallest viable base image

# Pick the right variant for your needs
FROM python:3.12-alpine     # ~50MB base
FROM python:3.12-slim       # ~120MB base - use if Alpine causes issues
FROM python:3.12            # ~1GB base - almost never needed

10. Combine RUN commands and clean up in the same layer

# Apt cache stays in Layer 1 even though Layer 2 deletes it
RUN apt-get update && apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

#  Truly clean - added and removed in the same layer
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*

11. Use multi-stage builds

# Build stage - has compiler, dev deps
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

# Production stage - minimal
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
RUN npm ci --omit=dev
CMD ["node", "dist/server.js"]

For compiled languages (Go, Rust), the savings are even more dramatic - a 300MB build stage becomes a 15MB runtime image.


Reliability

12. Use exec form for CMD and ENTRYPOINT

# Shell form - SIGTERM not forwarded to app
CMD node server.js

#  Exec form - app is PID 1, receives signals directly
CMD ["node", "server.js"]

13. Add a HEALTHCHECK

HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

Orchestrators use health checks to detect failures and restart unhealthy containers before users notice.

14. Set a non-zero STOPSIGNAL if needed

# Tell Docker to use SIGQUIT for graceful nginx shutdown
STOPSIGNAL SIGQUIT

Some applications expect a specific signal for graceful shutdown. Check your runtime's documentation.


Maintainability

15. Add OCI labels

LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.version="2.1.0"
LABEL org.opencontainers.image.authors="team@example.com"

16. Lint your Dockerfiles

Hadolint is the standard Dockerfile linter. It catches common mistakes and enforces best practices:

# Install hadolint
brew install hadolint       # macOS
# or: docker run --rm -i hadolint/hadolint < Dockerfile

# Lint your Dockerfile
hadolint Dockerfile

Common hadolint warnings:

  • DL3008 - Pin versions in apt-get install
  • DL3009 - Delete the apt-get lists after installing
  • DL3013 - Pin pip packages
  • SC2086 - Double-quote variables to avoid word-splitting

Integrate hadolint into your CI pipeline to catch issues before they reach production.


The Production Dockerfile Checklist

Before merging any Dockerfile change, verify:

  • Base image uses a specific version tag (not latest)
  • .dockerignore exists and excludes node_modules, .env, .git
  • Dependency files copied and installed before source code
  • RUN cleanup happens in the same layer as the install
  • No secrets in ENV or ARG
  • USER switches to non-root before CMD
  • CMD uses exec form ["..."]
  • EXPOSE documents the listening port
  • HEALTHCHECK is defined
  • Multi-stage build used if there's a compile/build step
  • hadolint passes with no errors
Exercise

Lint a Dockerfile

You want to run hadolint against the Dockerfile in the current directory using the official hadolint Docker image, without leaving behind a stopped container afterward. Which command is correct?

Summary

The most impactful practices, ranked:

  1. Non-root USER - biggest security win for one line of code
  2. Exec form CMD - critical for graceful shutdown
  3. Layer cache order - biggest build speed win
  4. .dockerignore - prevents accidental secret leaks and speeds up builds
  5. Multi-stage builds - biggest image size win for compiled apps
  6. HEALTHCHECK - automatic failure detection
  7. Version pinning - reproducible builds
  8. hadolint in CI - catches the rest before it ships
PreviousPrev
Next

Continue Learning

CMD & ENTRYPOINT

Define how your container starts. Understand exec vs shell form, the difference between CMD and ENTRYPOINT, and how to combine them for flexible, production-ready containers.

9 min·Easy

ENV & ARG

Inject variables into your builds and containers. Learn the critical difference between ENV (runtime) and ARG (build-time only), and how to use both safely.

7 min·Easy

WORKDIR & EXPOSE

Set working directories cleanly and document ports correctly. Plus LABEL, USER, VOLUME, and HEALTHCHECK - the supporting instructions every production Dockerfile needs.

7 min·Easy

Explore Related Topics

Ku

Kubernetes Tutorials

Orchestrate your Docker containers at scale

CI

CI/CD Tutorials

Automate Docker builds and deployments in pipelines

Try the Tool

Dockerfile Linter

Instantly lint your Dockerfile for best-practice violations and security issues.

On This Page

Best PracticesSecurityBuild Performance (Caching)Image SizeReliabilityMaintainabilityThe Production Dockerfile ChecklistSummary