Docker Best Practices and Security

advanced
16 min

Docker Best Practices and Security

You can now build images, run containers, wire up networks and compose multi-service apps. The final step is doing all of that safely and maintainably. Containers share the host kernel, so a careless configuration can turn one compromised container into a compromised server. This lesson collects the practices that separate hobby setups from production-grade Docker.

Run as a Non-Root User

By default, processes inside a container run as root. If an attacker escapes the application, they hold root inside the container — and any mounted volume or kernel vulnerability becomes far more dangerous. Create and switch to an unprivileged user in your Dockerfile:

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

Many official images already provide a user (for example node); using USER node costs nothing and closes a whole class of attacks.

Keep Images Small and Specific

Every package in an image is potential attack surface. Prefer slim or alpine base images, use multi-stage builds so compilers and dev dependencies never reach production, and maintain a thorough .dockerignore (exclude .git, node_modules, .env, logs). Pin base images to specific versions — node:20.14-alpine rather than node:latest — so builds are reproducible and upgrades are deliberate rather than accidental.

Never Bake Secrets into Images

Anything written in a Dockerfile or copied into an image is recoverable with docker history or by exporting layers. That includes secrets you later delete in another layer. Instead:

  • Pass runtime configuration through environment variables set at docker run time, not ENV in the Dockerfile.
  • Use Docker secrets or BuildKit secret mounts for build-time credentials: RUN --mount=type=secret,id=npmrc npm ci exposes the file only during that step.
  • Keep .env files out of images and out of version control.

Drop Privileges and Limit Resources

Docker lets you shrink what a container is allowed to do:

bash
docker run -d \ --read-only --tmpfs /tmp \ --cap-drop=ALL --cap-add=NET_BIND_SERVICE \ --security-opt no-new-privileges \ --memory=512m --cpus=1 \ --restart unless-stopped \ myapp:1.4.2

Here the filesystem is read-only except a scratch /tmp, all Linux capabilities are dropped except binding low ports, privilege escalation is blocked, and the container cannot starve the host of memory or CPU. Never use --privileged unless you fully understand why you need it, and avoid mounting the Docker socket (/var/run/docker.sock) into containers — that is effectively root on the host.

Scan and Update Continuously

Images age badly: yesterday’s secure base image accumulates CVEs. Build scanning into your workflow:

bash
docker scout cves myapp:1.4.2 # or: trivy image myapp:1.4.2

Rebuild images regularly to pick up patched base layers, sign images if your registry supports it, and pull only from trusted registries. In CI, fail the pipeline on critical vulnerabilities so insecure images never ship.

Operational Hygiene

  • Add a HEALTHCHECK so orchestrators know when a container is alive but broken.
  • One process per container: it keeps logs, restarts and scaling simple.
  • Log to stdout/stderr and let the logging driver handle shipping; avoid writing log files inside containers.
  • Use restart policies (unless-stopped) rather than ad-hoc scripts.
  • Label images with version and commit metadata (LABEL org.opencontainers.image.revision=$GIT_SHA) for traceability.
  • Clean up with docker system prune on build hosts to control disk usage.

A Security Checklist

Before shipping, verify: non-root user, pinned slim base image, multi-stage build, no secrets in layers, capabilities dropped, resource limits set, healthcheck defined, image scanned, and the Docker daemon itself patched and reachable only by trusted users (membership in the docker group is root-equivalent).

Key Takeaways

  • Run containers as non-root, drop capabilities, and consider read-only filesystems with --tmpfs for scratch space.
  • Small, pinned, multi-stage images reduce attack surface and make builds reproducible.
  • Secrets belong in runtime configuration or secret mounts, never in image layers.
  • Continuous scanning with Docker Scout or Trivy plus regular rebuilds keeps CVEs out of production.
  • Healthchecks, restart policies, stdout logging and one process per container keep operations predictable.

That wraps up the Docker course — you can now take an application from a bare Dockerfile to a hardened, production-ready deployment. For a natural next step, explore orchestration with Kubernetes or revisit Docker Compose to add healthchecks and resource limits to your existing stacks.

Docker Best Practices and Security - Docker | CodeYourCraft | CodeYourCraft