Beginner · Docker · topic 19 of 53 · 30 min
Image and container security
Explanation
Why container security matters
Containers share the host's kernel, so a container running as root with too many permissions is only a few steps away from controlling the whole server. Images also carry hundreds of packages you did not write, and any of them may have a known vulnerability, often called a CVE (Common Vulnerabilities and Exposures, a public ID for a security bug). Security work therefore has two parts: build a clean image, and run it with the least privilege it needs.
Build a clean image
- Minimal base image: slim, alpine or distroless. Fewer packages means fewer vulnerabilities and fewer tools for an attacker. A distroless image has no shell at all.
- Pin versions: FROM python:3.12-slim, never just python. For full reproducibility, pin the digest too: FROM python:3.12-slim@sha256:<digest>. A digest identifies the exact image content and cannot be moved like a tag.
- Multi-stage builds, so compilers and dev dependencies never reach the final image.
- Rebuild and redeploy regularly (for example weekly, from CI) so security patches in the base image reach production. Pinning without updating means you keep old vulnerabilities forever; tools like Dependabot or Renovate can open pull requests to bump pinned versions.
Run as a non-root user
By default, the process in a container runs as root (user ID 0). If an attacker breaks into your app, they are root inside the container, which makes escaping to the host much easier. Create a user in the Dockerfile and switch to it. Many official images already include one: the node images have a user called node.
RUN groupadd --system app && useradd --system --gid app --uid 10001 app
COPY --chown=app:app . /app
USER appNo secrets in images
Everything in an image can be read by anyone who can pull it: files in any layer, ENV values and build ARGs (docker history shows them). So secrets such as a private npm token or an API key must never go into ENV, ARG, or a copied file. For secrets needed DURING the build, use BuildKit build secrets, which are mounted only for one RUN step and never saved in a layer.
# syntax=docker/dockerfile:1
FROM node:24-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci --omit=devFor secrets needed at RUN time, pass them when the container starts: through the platform's secret store (Docker Swarm or Compose secrets, Kubernetes Secrets, AWS Secrets Manager and similar), mounted as files under /run/secrets. Environment variables are common and acceptable for many teams, but remember that they show up in docker inspect.
Run with least privilege
docker run -d --name shop-api \
--read-only --tmpfs /tmp \
--cap-drop ALL --cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges \
--user 10001 --memory 512m --pids-limit 200 \
-p 8080:3000 ghcr.io/acme/shop-api:2.1.0| Option | What it prevents |
|---|---|
| --read-only (+ --tmpfs /tmp) | Writing or changing files in the container, such as dropping a malicious script |
| --cap-drop ALL | Linux capabilities (pieces of root's power, like changing file ownership or network settings). Add back only what you need |
| --security-opt no-new-privileges | Gaining more privileges through setuid programs |
| --user 10001 | Running as root, even if the image forgot USER |
| --memory, --pids-limit | One container exhausting memory or starting endless processes |
Scan images
docker scout cves ghcr.io/acme/shop-api:2.1.0 # Docker Scout: list known CVEs
trivy image ghcr.io/acme/shop-api:2.1.0 # Trivy, a popular open-source scannerA scanner compares the packages in your image with databases of known vulnerabilities. Not every finding is urgent: check whether a fix exists and whether the affected package is actually used. Often the fix is simply rebuilding on a newer base image.
Sign images
How does a server know an image really came from your CI and was not swapped in the registry? By signing it. Cosign, from the Sigstore project, signs an image (by digest) and stores the signature in the registry; at deploy time, cosign verify or a Kubernetes admission policy checks the signature before the image is allowed to run.
Common mistakes
- Deleting a secret file in a later RUN: it still exists in the earlier layer. Use build secrets.
- Pinning versions once and never updating them. Pin AND update on a schedule.
- Ignoring scanner output because there are many findings. Start by fixing critical issues with available fixes, and switch to a smaller base image to cut the noise.
- Running --privileged "to make it work". Find the one capability or device the app really needs instead.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Image and container security · 1/7
Containers share the host kernel, so a weak image or a too-powerful container can put the whole server at risk.
Prefer a video by a teacher? Find free videos on “docker security best practices non root image scanning trivy” on YouTube ↗
In real life: Passing a bank's security review
A Mumbai fintech wants to integrate with a large bank, and the bank's security team reviews its deployment. The startup's images used to run as root on a full Debian base, with an npm token in an ARG. They moved to multi-stage builds on node:24-slim, added USER node, used a build secret for the token, and set --read-only and --cap-drop ALL in production. CI now runs Trivy on every image, fails on critical CVEs with fixes, rebuilds weekly, and signs images with cosign. The review passes, and the scanner report becomes part of their audit evidence.
trivy image --severity CRITICAL --ignore-unfixed --exit-code 1 ghcr.io/acme/pay-api:4.2.0Practice (0/4 done)
Which Dockerfile line makes the container run as a non-root user?
Write risky_flags(command) that checks a docker run command line and returns a sorted list of finding codes (each code once): - "privileged": the --privileged flag - "docker-socket": any value that contains /var/run/docker.sock - "root-user": -u or --user whose user part (before ":") is root or 0 - "all-capabilities": --cap-add with the value ALL (any case) - "host-network": --network or --net with the value host Flags may be written as "--flag value" or "--flag=value". Split the command on spaces; values never contain spaces.
Harden this Dockerfile and write the docker run command you would use in production. Explain each change in one line. FROM node WORKDIR /app COPY . . ARG NPM_TOKEN RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc && npm install ENV JWT_SECRET=supersecret123 EXPOSE 3000 CMD npm start
A Dockerfile copies .env in one RUN step and deletes it with rm in the next RUN. Is the secret safe?
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Running containers: ports, volumes, networks and limitsDocker · same module
- Docker: packaging your appBackend Development · Deployment and scaling
- Containers, images and registriesCloud Computing (AWS, Azure, Google Cloud) · Containers in the cloud
- Software supply chain securityDevSecOps
- Docker images for Go servicesGo Programming (backend and cloud) · CLI tools and Go in cloud and DevOps
- Kubernetes securityDevSecOps
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Why is mounting /var/run/docker.sock into a container dangerous?
Answer: It lets the container control the Docker daemon, and so effectively the whole host
With the socket, a container can start a new privileged container that mounts the host file system.
What does pinning an image by digest (image@sha256:...) give you?
Answer: The exact same image content every time, because a digest cannot be moved like a tag
A digest is a content hash. Remember to update pins on a schedule so patches still arrive.
Which option set follows least privilege for a normal web API?
Answer: --read-only --cap-drop ALL --security-opt no-new-privileges
These remove write access, Linux capabilities and privilege escalation that the app does not need.
Which tools scan container images for known vulnerabilities?
Answer: Docker Scout and Trivy
Docker Scout and Trivy compare image packages with vulnerability databases. cosign signs images.
You need a private token only while installing packages during the build. What is the right approach?
Answer: A BuildKit secret mount: RUN --mount=type=secret,...
Secret mounts exist only for that RUN step and are never stored in an image layer or the history.
What is the difference between an image and a container?Related
Answer: An image is the package (recipe); a container is a running copy of it
You build an image once and start one or many containers from it.
A container edits a file that came from the image. What happens?Related
Answer: The file is copied into the container's writable layer and changed there; the image is unchanged
Copy-on-write keeps image layers read-only and shared.
What is Google Cloud's current registry for container images?Related
Answer: Artifact Registry
Artifact Registry replaced Container Registry.
Which tool is used to sign and verify container images?Related
Answer: cosign
cosign, from the Sigstore project, signs images and verifies signatures.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Docker”. The module exam comes next.
- Module 4 exam: Docker · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Kubernetes fundamentals. 53 topics left in the course before the final exam.