Drop Docker capabilities and set no-new-privileges
docker run \ --cap-drop=ALL \ --security-opt=no-new-privileges:true \ myimageservices: app: cap_drop: [ALL] security_opt: - no-new-privileges:trueAdd back only what fails without.
Why it matters
Section titled “Why it matters”A default Docker container isn’t unprivileged — it gets a default set of around
fourteen capabilities, whether or not the workload uses any of them. A Node app
serving HTTP holds CAP_CHOWN, CAP_SETUID, CAP_SETGID, CAP_NET_RAW,
CAP_DAC_OVERRIDE and others, permanently, for no reason.
Each is a tool waiting for an attacker who gets code execution:
CAP_DAC_OVERRIDE— bypass file permission checks entirely. Read any file in the container regardless of mode.CAP_SETUID/CAP_SETGID— become any user, which turns a compromise of your unprivileged app user into container root.CAP_NET_RAW— craft raw packets. Enables ARP spoofing against other containers on the same bridge network.CAP_CHOWN— change file ownership, including on bind mounts.
--cap-drop=ALL removes the lot. Most containers need nothing added back, because
most containers just run a process and listen on a socket.
no-new-privileges is the cheapest line here
Section titled “no-new-privileges is the cheapest line here”--security-opt=no-new-privileges:trueThis sets the kernel’s no_new_privs bit, which means a process can never gain
privileges through execve — setuid binaries stop working as an escalation
path.
It matters because dropping capabilities from the container doesn’t stop a setuid
binary inside the image from re-elevating. If your image ships sudo, su,
ping, mount, or any distro package with a setuid bit, an attacker who lands as
your unprivileged user can potentially use it. no_new_privs closes that door
for everything at once, and it breaks essentially nothing — no normal application
gains privileges through exec.
If you set one flag from this page, set this one.
You can also just remove the setuid bits at build time:
RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} + || trueWhich ones you might actually need
Section titled “Which ones you might actually need”| Need | Capability |
|---|---|
| Bind port < 1024 | NET_BIND_SERVICE — but prefer a high port + -p 80:8080 |
ping |
NET_RAW — or just don’t ping from production containers |
| Change file ownership at startup | CHOWN — better: fix it at build time |
| Drop from root to a service user at startup | SETUID, SETGID — better: use USER |
Note how many entries say “better”. Most capability requirements are a
consequence of the container starting as root and then de-escalating — a pattern
that USER in the Dockerfile removes
entirely. Fix that and the capability list usually collapses to nothing.
Related
Section titled “Related”- Never run with –privileged — the flag that grants all of these at once.
- Run containers as a non-root user — removes most reasons to need any.