Docker Security
Secure Docker images and containers: non-root users, read-only filesystems, dropped capabilities, secrets handling, and image scanning.
Containers share the host kernel, so a weak configuration can turn a bug in your application into a compromise of the host. Docker security is about reducing what an attacker can do at every layer: the image contents, the container's runtime privileges, the secrets it holds, and the daemon that manages it. Most improvements are small configuration changes that cost nothing at runtime.
Where the Risk Comes From
| Layer | Typical risk | Main defence |
|---|---|---|
| Image | Vulnerable packages, leaked secrets, untrusted base | Minimal base images, scanning, pinned versions |
| Container runtime | Running as root, excess capabilities, privileged mode | Non-root user, dropped capabilities, read-only filesystem |
| Host and daemon | Exposed daemon socket, outdated kernel or Engine | Restrict socket access, patch the host |
| Network | Unnecessary published ports, flat networks | Publish minimally, segment networks |
| Secrets | Credentials in images, environment variables, or Git | Secret mounts, secret managers |
Run as a Non-Root User
By default a container process runs as root (UID 0) inside the container. If it escapes isolation, it may have root-level access on the host. Create and switch to an unprivileged user in the Dockerfile:
FROM eclipse-temurin:21-jre
RUN groupadd --system app && useradd --system --gid app --no-create-home app
WORKDIR /app
COPY --chown=app:app target/app.jar app.jar
USER app
ENTRYPOINT ["java", "-jar", "app.jar"]
You can also enforce it at run time:
docker run --user 10001:10001 my-app:1.0
docker exec my-app id
# uid=10001 gid=10001
Restrict Runtime Privileges
docker run -d --name hardened \
--read-only \
--tmpfs /tmp \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
--pids-limit 200 \
--memory 512m --cpus 1 \
my-app:1.0
| Option | Effect |
|---|---|
--read-only |
Root filesystem is mounted read-only; writes go only to declared volumes or tmpfs |
--cap-drop ALL |
Removes all Linux capabilities; add back only what is required |
--security-opt no-new-privileges:true |
Prevents processes from gaining extra privileges, such as through setuid binaries |
--pids-limit |
Limits the number of processes, mitigating fork bombs |
--memory, --cpus |
Limits resource consumption |
Docker applies a default seccomp profile that blocks a number of dangerous system calls. Do not disable it with --security-opt seccomp=unconfined unless you have a specific, understood reason.
Avoid Privileged Mode and Dangerous Mounts
--privileged gives the container nearly all capabilities and access to host devices. Avoid it. Likewise avoid mounting /var/run/docker.sock into containers, since access to the socket is effectively root on the host, and avoid bind-mounting sensitive host paths like / or /etc.
Handle Secrets Properly
Anything placed in a Dockerfile ENV, ARG, or COPY ends up in image metadata or layers, which can be extracted by anyone with the image. Environment variables are also visible through docker inspect.
For build-time secrets, use BuildKit secret mounts. The secret is available only during that RUN and is not stored in a layer:
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
docker build --secret id=npm_token,src=./npm_token.txt -t my-app:1.0 .
For runtime secrets, use Compose secrets, Docker Swarm secrets, or your orchestrator's secret store, which mount secrets as files:
services:
app:
image: my-app:1.0
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txt
The application then reads /run/secrets/db_password.
Choose and Scan Base Images
Smaller images contain fewer packages and therefore fewer vulnerabilities. Prefer official or verified publisher images, slim variants, or distroless images, and pin the version. Scan images regularly:
docker scout cves my-app:1.0
# or with other scanners such as Trivy:
trivy image my-app:1.0
Rebuild images regularly to pick up patched base layers, rather than patching running containers.
Image Trust
Docker Content Trust signs and verifies image tags:
export DOCKER_CONTENT_TRUST=1
docker pull myorg/my-app:1.0 # fails if the tag is not signed
Pulling by digest (image@sha256:...) also guarantees you run exactly the content you reviewed.
Common Mistakes
- Running as root. The default, and the most common weakness.
- Using
--privilegedto "make it work". Find and grant the specific capability or device needed instead. - Baking secrets into images or Git. Rotate any secret that has been committed or published in an image.
- Using unpinned or unknown base images.
latestand random Docker Hub images can change or contain malware. - Exposing the Docker daemon over plain TCP. An unauthenticated remote API is remote root.
Best Practices
- Use minimal, pinned, regularly rebuilt base images and scan them in CI.
- Run as non-root, drop all capabilities, use
no-new-privileges, and set a read-only root filesystem when the app allows it. - Keep secrets out of images and environment variables where possible; mount them at runtime.
- Set CPU, memory, and PID limits on every container.
- Keep the host kernel and Docker Engine patched, and restrict who can use the Docker socket.
Interview Questions
1. Why is running a container as root a problem?
If an attacker breaks out of the container or exploits a misconfiguration, root inside the container maps to root on the host unless user namespaces are used. A non-root user limits the damage and prevents actions like modifying system files or binding privileged ports.
2. What are Linux capabilities and how does Docker use them?
Capabilities split root's powers into discrete privileges such as NET_BIND_SERVICE or SYS_ADMIN. Docker gives containers a reduced default set. You can tighten it with --cap-drop ALL and add back only what is needed.
3. How should you handle secrets in Docker?
Never put them in the Dockerfile, image layers, or Git. Use BuildKit secret mounts for build-time needs and mounted secret files from Compose, Swarm, Kubernetes, or a secret manager at runtime. Environment variables are convenient but visible via docker inspect.
4. What does --privileged do and why avoid it?
It grants the container almost all capabilities, access to host devices, and relaxed security profiles. A compromised privileged container can often take over the host, so use specific --cap-add or --device options instead.
5. How do you find vulnerabilities in an image?
Run an image scanner such as Docker Scout (docker scout cves) or Trivy against the image, ideally in CI, and fail the build on severe findings. Fix them by updating the base image and dependencies and rebuilding.
6. Why is mounting /var/run/docker.sock dangerous?
The socket is the Docker daemon's control interface. A container with access can start privileged containers and mount the host filesystem, which is equivalent to root on the host.
Summary
- Reduce privileges: non-root user, dropped capabilities,
no-new-privileges, read-only filesystem, resource limits. - Keep images minimal, pinned, scanned, and rebuilt often.
- Keep secrets out of images; use secret mounts and runtime secrets.
- Avoid
--privileged, an exposed daemon, and the Docker socket inside containers.
Continue Learning
Previous: Docker Multi Stage Build | Next: Docker Image Optimization
Was this guide helpful?
CodeWithVenu is free to read, with no paywall. If this guide saved you time or helped you prepare, a small voluntary contribution helps me write more practical tutorials, architecture guides, and interview-prep content.