Docker containers run with a default set of Linux capabilities. The right way to harden a container is to drop the unneeded capabilities with --cap-drop ALL --cap-add <specific>. The right ones to keep depend on the workload. The team that drops unneeded capabilities has the right container security posture.
Table of contents
- What Linux capabilities are
- The right way to drop capabilities
- The right capabilities to keep per workload
- The right Docker Compose config
- The dangerous capabilities to drop
- The right way to verify capabilities
- FAQ
What Linux capabilities are
Linux capabilities are a fine-grained way to grant root-like powers to a process without giving it full root. Examples: CAP_NET_BIND_SERVICE (bind to ports below 1024), CAP_CHOWN (change file ownership), CAP_NET_RAW (use raw sockets), CAP_SYS_ADMIN (mount filesystems, etc.).
The right answer is that a default Docker container runs with about 14 capabilities, not full root. The right answer for security is to drop the ones the workload does not need.
The right way to drop capabilities
The right Docker invocation:
docker run --cap-drop ALL --cap-add CHOWN --cap-add NET_BIND_SERVICE nginx
This drops all capabilities, then adds back only what nginx needs. The wrong answer is to leave the defaults and hope the workload does not abuse them.
The right capabilities to keep per workload
For a web server: CHOWN, DAC_OVERRIDE, FOWNER, NET_BIND_SERVICE, SETUID, SETGID.
For a database: CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID, SYS_CHROOT (for chroot for security).
For a CI runner: CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID, KILL, NET_BIND_SERVICE.
The right answer is to look at the container’s actual needs, not assume the default is safe.
The right Docker Compose config
services:
app:
image: myapp
cap_drop:
- ALL
cap_add:
- CHOWN
- NET_BIND_SERVICE
read_only: true
security_opt:
- no-new-privileges
The right answer is to combine cap_drop with read_only: true and no-new-privileges for defense in depth.
The dangerous capabilities to drop
The right answer is to drop these capabilities if your workload does not need them: SYS_ADMIN (mount, swapon, etc.), NET_ADMIN (configure network), SYS_PTRACE (ptrace other processes), NET_RAW (raw sockets, often used for ping), SYS_MODULE (load kernel modules), SYS_RAWIO (raw I/O access).
The right way to verify capabilities
The right way to see what capabilities a running container has:
docker exec container-name cat /proc/1/status | grep Cap
The output is the capability bitmask. The right answer is to compare against a known-good set and to verify nothing unexpected is enabled.
FAQ
What is the default capability set for Docker?
About 14 capabilities: CHOWN, DAC_OVERRIDE, FSETID, FOWNER, MKNOD, NET_RAW, SETGID, SETUID, SETFCAP, SETPCAP, NET_BIND_SERVICE, SYS_CHROOT, KILL, AUDIT_WRITE.
Is SYS_ADMIN dangerous?
Yes. SYS_ADMIN allows mount, swapon, pivot_root, and many other root-like operations. The right answer is to drop it unless the container needs it.
What is —privileged?
It grants all capabilities and disables most security restrictions. The right answer is to never use it in production.
What is no-new-privileges?
A security option that prevents the container from gaining new privileges. The right answer is to enable it for all production containers.
What is AppArmor vs capabilities?
AppArmor is a mandatory access control system. Capabilities are Linux kernel features. The right answer is to use both for defense in depth.
If you are sizing the infrastructure for the kind of project this post covers, the RunxBuild hosting calculator is the right place to model the line items. The compute, the memory, the storage, the bandwidth, the database - each one is a separate number, and the team’s mental model for the platform is the sum of those numbers. The RunxBuild dashboard is where the team sees the actual usage in one place.
Useful related references: