Docker best practices
The habits that make images small, fast, and safe.
Everything in this course reduces to a handful of habits that make images small, builds fast, and containers safe. None are exotic; the value is applying them every time. This lesson is the checklist — each item links back to a mechanism you have already seen, gathered so you can run down the list before you ship an image.
Build habits
Start from the smallest base that runs your app and pin it by digest. Use a multi-stage build so compilers and package managers never reach the final image. Order instructions so rarely-changing steps (dependency installs) sit above frequently-changing ones (source copies) to keep the cache warm. Add a .dockerignore listing .git, node_modules, and any secrets so they never enter the build context. Use the exec form of CMD/ENTRYPOINT so your process is PID 1 and receives signals.
FROM node:22-alpine@sha256:... # minimal + pinnedWORKDIR /appCOPY package*.json ./ # cache-friendly orderRUN npm ci --omit=devCOPY . .USER node # non-rootHEALTHCHECK CMD wget -qO- localhost:3000/healthz || exit 1CMD ["node","server.js"] # exec form -> proper PID 1
Runtime habits
Run non-root and drop Linux capabilities you do not need. Make the root filesystem read-only and grant explicit tmpfs/volumes for the few writable paths. Deliver credentials as mounted secrets, never as -e (which leaks into inspect and logs). Set resource limits (--memory, --cpus, --pids-limit) so one container cannot starve the host, and define a healthcheck so orchestrators know real readiness. One tag for humans, a digest for the machine that runs it.
$ docker run -d \--user 10001:10001 --cap-drop ALL \--read-only --tmpfs /tmp \--memory 256m --cpus 1 --pids-limit 200 \--health-cmd 'wget -qO- localhost:3000/healthz || exit 1' \registry.internal/payments-api@sha256:9f2a...