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 --privileged to "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. latest and 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.