Skip to content

Best Practices

First PublishedByAtif Alam

Follow these practices to keep images small, secure, and maintainable.

  • Prefer alpine or slim variants (e.g. node:20-alpine, python:3.12-slim) over full distros when possible.
  • Multi-stage builds keep build tools out of the final image.
  • Run docker image ls and trim unused images with docker image prune.

Running as root inside the container increases risk. Create and use a non-root user in the Dockerfile:

FROM node:20-alpine
RUN addgroup -g 1000 app && adduser -u 1000 -G app -D app
WORKDIR /app
COPY --chown=app:app . .
USER app
CMD ["node", "server.js"]

Define HEALTHCHECK so Docker and orchestrators (e.g. Kubernetes) can detect when the app is unhealthy:

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -q -O- http://localhost:8080/health || exit 1

In Compose you can use healthcheck: with test, interval, timeout, retries, start_period.

  • Do not bake secrets into images or pass them via ENV in the Dockerfile. Use Docker secrets, mounts, or your orchestrator’s secret store (e.g. Kubernetes Secrets).
  • Use read-only mounts where possible (:ro in bind mounts).
  • Order Dockerfile so that less frequently changed steps (dependencies, base setup) come before frequently changed ones (app code). This maximizes cache hits and speeds up builds.
  • Use .dockerignore so unneeded files (e.g. node_modules, .git) are not in the build context.

Scan images for known vulnerabilities in base layers and dependencies:

  • Docker Scout (or legacy docker scan) for local scans.
  • Registry or CI integrations (e.g. ECR scanning, Snyk, Trivy) for automated checks.
  • Small bases, multi-stage builds, and .dockerignore keep images lean and builds fast.
  • Non-root user and HEALTHCHECK improve security and observability.
  • Secrets belong in secret stores or mounts, not in images or ENV. Scan images in CI or registry for vulnerabilities.