Linux capabilities

Root's powers are split into capabilities: CHOWN, SETUID, NET_RAW and so on. Docker gives 14 of them to every container by default; most services need none.

How it works

cap_drop: [ALL] removes them and cap_add gives back only those a program proves it needs; privileged: true grants all of them and much more, and is almost never right. Images that start as root to fix permissions and switch user (nginx, postgres) need CHOWN, SETUID and SETGID; an app that already runs as an unprivileged user needs none. no-new-privileges stops setuid binaries from raising privileges.

What breaks

Without a capability, the call fails with Operation not permitted: chown of a folder, gosu or setgid when switching user. Add back exactly that one, or choose an image built to run unprivileged; keeping the default set leaves a compromised process more ways to escalate.

In compose.yaml

services:
  frontend:
    image: nginx:1.27-alpine
    cap_drop: [ALL]
    cap_add: [CHOWN, SETUID, SETGID]
    security_opt:
      - "no-new-privileges:true"

See it break, then fix it

Write the Dockerfile or the compose file, type the docker commands, and watch a simulated server run them through reboots, upgrades and traffic. Free to start.