After this lesson, you will be able to: Harden a container for production: run as a non-root user, use a read-only filesystem and dropped capabilities, keep secrets out of images, and scan for vulnerabilities.
A container is not a security boundary by default. The defaults (root user, writable filesystem, all Linux capabilities, secrets baked into layers) are convenient and dangerous. This lesson is the set of changes that turn a working image into one you are comfortable running in production, where a compromised container should not become a compromised host.
By default a container runs as root, and that root is (largely) the host's root. If an attacker breaks out of a process running as root in the container, they are far closer to owning the host. The image filesystem is writable, so a compromised process can drop tooling and persist. The container holds a broad set of Linux capabilities it almost never needs. And secrets copied into the image live forever in its layers, readable by anyone who pulls it. Each of these has a one-line fix.
The four changes that cover most of the risk.
# In the Dockerfile: create and switch to a non-root userFROM node:20-slimRUN useradd --system --uid 1001 appuserWORKDIR /appCOPY --chown=appuser:appuser . .RUN npm ci --omit=devUSER appuser # everything after this runs as appuser, not rootCMD ["node", "server.js"]# At run time (or in Compose), lock it down further:# docker run --read-only \ # filesystem is read-only# --tmpfs /tmp \ # except an explicit writable temp# --cap-drop ALL \ # drop all Linux capabilities# --security-opt no-new-privileges \# myimage:1.2.3
Catch known CVEs in your base image and dependencies as part of the build.
# Trivy: scan a built image for known vulnerabilitiestrivy image myimage:1.2.3# Fail CI on High/Critical findings:trivy image --exit-code 1 --severity HIGH,CRITICAL myimage:1.2.3# Docker Scout (built into Docker Desktop / CLI):docker scout cves myimage:1.2.3# Smaller base = smaller attack surface:# node:20-slim or distroless instead of node:20# alpine where the libc difference is acceptable# Fewer packages in the image means fewer CVEs to patch.
Shipping images that run as root because it 'just works.' Add a USER line; most apps need no root at all. Baking secrets into the image with ENV or COPY, then assuming a later `RUN rm` removed them. Layers keep the original. Never scanning, so a known-critical CVE in the base image rides to production. Scan in CI and fail the build. Using a giant base image (the full `node:20`) when slim or distroless would cut the attack surface and image size dramatically. Forgetting `.dockerignore`, so `.env`, `.git`, and local junk get copied into the build context (and sometimes the image).
Pick the correct answer.
Sign in and purchase access to unlock this lesson.