Topic 7.2
Container Security Fundamentals
In one line
A container's isolation is strong but not absolute — a handful of concrete habits close the most common, most exploited gaps.
Think of it like this
Locking your front door AND not leaving a spare key under the mat AND not writing your alarm code on a sticky note by the door. Each individual habit here is simple; skipping any one of them undermines the whole point of the others.
Key ideas
- 01
NEVER run a container's main process as root unless there's a specific, well-understood reason to (Phase 2.4 introduced this) — if an attacker manages to execute arbitrary code inside your container (via an app vulnerability), running as a non-root user meaningfully limits what that code can do WITHIN the container, and makes a container-escape attempt significantly harder.
- 02
Scan your images for known vulnerabilities regularly — tools like
docker scout, Trivy, or Snyk check your image's OS packages and application dependencies against public vulnerability databases (CVEs) and flag known-exploitable versions BEFORE you deploy, not after an incident. - 03
Pin base image versions explicitly (Phase 1.4, Phase 2.4) — an unpinned
latestbase image can silently introduce a NEW vulnerability (or a breaking change) into your build the next time you rebuild, with zero code change on your own part to explain why something suddenly broke or got flagged by a scanner. - 04
Never bake secrets (API keys, database passwords, private certificates) into an image layer, ever — Phase 2.3 already covered why this is unrecoverable even with a later 'delete' instruction. Use runtime environment variables, mounted secret files, or a dedicated secrets manager instead, never a COPY or hardcoded ENV in the Dockerfile itself.
- 05
Set resource limits (CPU and memory — Phase 6.2 covered memory specifically) on every container that matters — an unbounded container is a potential denial-of-service vector against its own host, whether from a genuine bug (a memory leak) or a deliberate attack (a resource-exhaustion attempt from inside a compromised container).
- 06
Keep your Docker Engine itself updated — container-escape vulnerabilities in the Docker runtime/kernel interaction do occasionally get discovered and patched, exactly like any other piece of security-critical software; an outdated Docker Engine is itself a real attack surface independent of anything in your own images.
- 07
Read-only root filesystems (
docker run --read-only) prevent a container from writing to its OWN filesystem at all beyond explicitly mounted writable volumes — a strong hardening step for apps that genuinely don't need to write anywhere except a specific, intentional location (like a temp directory you explicitly mount as writable).
In your stack
- →
A hardened production Spring Boot
docker runcombines several of this topic's habits at once:docker run --read-only --tmpfs /tmp -m 512m --user 1000:1000 -p 8080:8080 myapp:v1— read-only root filesystem, a writable tmpfs for Java's own temp file needs, a memory ceiling, and an explicit non-root user, all stacked together.
Code & diagrams
Several security habits from this topic, applied together to one command.
# Read-only root filesystem + a writable tmpfs for the JVM's own temp file needs
docker run -d --name myapp \
--read-only \
--tmpfs /tmp \
-m 512m \
--user 1000:1000 \
-p 8080:8080 \
myapp:v1
# Scan an image for known vulnerabilities BEFORE deploying it
docker scout cves myapp:v1
# Confirm the container is genuinely running as the expected non-root user
docker exec myapp id
# uid=1000 gid=1000None of these habits are individually sufficient — they're layers, and each one shrinks the blast radius the others leave behind.
Explain it without notes
Why does running a container as a non-root user meaningfully limit the damage of a successful application-level exploit, even though the container's isolation from the HOST is unrelated to which user is running inside it?
Why is scanning your OWN application code for vulnerabilities not sufficient — why do you also need to scan the base image?
Practice
Run docker scout cves (or an equivalent tool like Trivy) against any image you've built and review what it reports — even a 'clean' base image often has a few low-severity findings worth understanding.
Take a container you're currently running as root and convert it to run as a specific non-root user, confirming with docker exec <container> id that the change took effect.
Trade-offs
- ↔
Read-only root filesystems and strict non-root users occasionally require extra setup work (finding and explicitly mounting every path an app genuinely needs to write to) — a real, worthwhile cost for anything running in production, and often unnecessary friction to bother with for a quick local experiment.
Done when you can
Every container I run in anything resembling production uses a non-root user.
I scan my images for known vulnerabilities before deploying them.
I set explicit resource limits on containers that matter.
I never bake secrets into an image layer.