Safeguard
Container Security

Docker Security Pro Tips: Hardening Beyond the Basics

You already use a non-root user and a slim base. These are the pro-level Docker hardening tips — read-only filesystems, dropped capabilities, and the docker.sock trap — that actually stop breakouts.

Marcus Chen
Cloud Security Engineer
5 min read

Docker's defaults optimize for developer convenience, not security. Containers run as root, with a broad set of Linux capabilities, a writable root filesystem, no resource limits, and — far too often — the Docker socket mounted straight in. Each default is a small convenience and a large piece of attack surface. The runc breakout CVE-2024-21626 (January 2024) and its 2019 predecessor CVE-2019-5736 both showed that a container escape is a genuine, recurring risk, not a theoretical one, and the difference between a contained incident and a host compromise usually comes down to the flags you set at run time. This guide assumes you already use a non-root user and a minimal base image, and covers the next layer of hardening most teams skip.

Tip 1: Make the root filesystem read-only

A read-only root filesystem stops an attacker from writing a webshell, a cron job, or a modified binary after exploiting your app. Mount tmpfs for the few paths that genuinely need to be writable.

docker run --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  myapp:secure

Tip 2: Drop every capability, add back only what you need

Containers start with a default capability set most workloads never use. Drop all of them and re-add only the specific capability required — for a web server binding to a low port, that is just NET_BIND_SERVICE.

docker run \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --security-opt=no-new-privileges \
  myapp:secure

no-new-privileges prevents a setuid binary inside the container from ever granting more privilege than the process started with.

Tip 3: Never mount the Docker socket into a container

Mounting /var/run/docker.sock into a container hands that container root on the host, because the Docker daemon runs as root and the socket is its full API. This is the single most common self-inflicted container escape. If a workload needs to build images, use a rootless builder such as BuildKit or Kaniko instead.

# Do NOT do this — it is a host takeover waiting to happen
docker run -v /var/run/docker.sock:/var/run/docker.sock ci-runner

Tip 4: Set resource limits so one container cannot starve the host

A container with no limits can exhaust host memory, CPU, or process tables and take neighbors down with it. Bound them explicitly.

docker run \
  --memory=512m --memory-swap=512m \
  --cpus=1.0 \
  --pids-limit=200 \
  myapp:secure

Tip 5: Pin base images by digest and add a HEALTHCHECK

A mutable tag like node:22 can point at a different image tomorrow. Pin the digest so your supply chain is reproducible, and add a health check so orchestrators can tell a hung container from a healthy one.

FROM node:22-bookworm-slim@sha256:1a2b3c4d5e6f
HEALTHCHECK --interval=30s --timeout=3s \
  CMD ["node", "healthcheck.js"]

Docker hardening checklist

ControlFlag or directive
Non-root userUSER 10001 in Dockerfile
Read-only root FS--read-only plus --tmpfs
Least-privilege caps--cap-drop=ALL --cap-add=...
No privilege escalation--security-opt=no-new-privileges
Resource bounds--memory, --cpus, --pids-limit
No host socketnever mount /var/run/docker.sock
Reproducible baseFROM image@sha256:...
Liveness signalHEALTHCHECK directive

These same controls map directly onto Kubernetes securityContext fields — readOnlyRootFilesystem, runAsNonRoot, allowPrivilegeEscalation: false, and capabilities.drop — so hardening a Dockerfile and hardening a manifest are the same discipline expressed twice.

How Safeguard scans your images

Safeguard scans both what is inside the image and how it is configured to run. It parses each filesystem layer to build a component inventory and match it against vulnerability data, then runs reachability analysis so the findings that surface are the ones exploitable from code your service actually executes, not every CVE in the tree. It also flags hardening misconfigurations — running as root, a writable root filesystem, a mounted Docker socket, missing capability drops — before the image reaches a registry. Run the scanner locally or in CI with the Safeguard CLI, gate pull requests with Secure Containers, and enforce the equivalent runtime posture in your manifests with infrastructure-as-code scanning. If you are comparing platforms, the Safeguard vs Aqua breakdown covers how prioritization differs from raw policy enforcement.

Frequently Asked Questions

Is running rootless Docker enough on its own?

Rootless Docker meaningfully reduces the impact of a daemon compromise, but it does not replace per-container hardening. You still want a read-only filesystem, dropped capabilities, and resource limits, because those protect against an application-level compromise regardless of how the daemon runs.

Why is mounting the Docker socket so dangerous if my container is non-root?

Because the socket talks to a daemon that runs as root. Any process that can reach the socket can ask the daemon to start a new privileged container that mounts the host filesystem, which is a full host takeover. The container's own user does not matter.

Do read-only root filesystems break applications that write logs or caches?

They only break apps that write to unexpected paths. Send logs to stdout, and mount tmpfs or a named volume for genuine scratch space. Most services need one or two writable paths, which you allowlist explicitly rather than leaving the whole filesystem writable.

Should I set these flags in Docker or in Kubernetes?

Set them wherever the container actually runs. In production that is usually the Kubernetes securityContext; the docker run flags here are the equivalent for local runs and Compose. Enforce them as policy so a workload cannot ship without them.

Want these checks running on every build? Connect a repository at app.safeguard.sh/register, and find the full hardening and scanning setup in the documentation at docs.safeguard.sh.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.