Standardmäßig starten die meisten Docker-Container mit weitreichenden Rechten: Der Prozess im Container läuft als root (UID 0), das gesamte Dateisystem ist beschreibbar und der Kernel gewährt zahlreiche Linux-Capabilities. Gelingt einem Angreifer die Code-Execution über eine Sicherheitslücke in der Web-App, kann dies zum Ausbruch auf das Hostsystem führen. Mit wenigen standardisierten Direktiven lässt sich dieses Risiko minimieren.
1. Read-Only Root Filesystem (Dateisystem sperren)
Die meisten Webanwendungen müssen zur Laufzeit keine Binärdateien oder Bibliotheken verändern. Mit read_only: true verbieten wir jegliche Modifikation am Container-Image. Benötigt die Anwendung temporäre Schreibberechtigungen (z. B. für Caches oder Session-Dateien), mounten wir flüchtige tmpfs-Speicher im RAM:
services:
webapp:
image: my-app:latest
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m
- /run:rw,noexec,nosuid,size=16m
security_opt:
- no-new-privileges:true
Effekt: Ein Angreifer kann keine Schadsoftware, Skripte oder Rootkits im Dateisystem des Containers persistieren.
2. Rechte-Eskalation unterbinden (`no-new-privileges`)
Das Flag no-new-privileges:true verhindert, dass Prozesse innerhalb des Containers über SUID- oder SGID-Binaries (wie sudo oder modifizierte Executables) zusätzliche Berechtigungen erlangen können. Es gehört standardmäßig in jeden Produktions-Stack.
3. Linux Capabilities minimieren (`cap_drop: ALL`)
Linux unterteilt Root-Rechte in fein granulierte Capabilities. Docker gewährt standardmäßig rund 14 davon (z. B. CAP_CHOWN, CAP_MKNOD). Für maximale Sicherheit droppen wir alle Capabilities und fügen ausschließlich die zwingend benötigten hinzu:
services:
nginx-secure:
image: nginx:alpine
cap_drop:
- ALL
cap_add:
# Erlaubt das Binden an Ports unter 1024 (z. B. Port 80/443)
- NET_BIND_SERVICE
# Erlaubt Nginx das Ändern der Worker-UID
- SETUID
- SETGID
4. Non-Root User erzwingen
Führe Prozesse niemals als Root aus. In Compose lässt sich dies über die user-Direktive erzwingen:
services:
node-app:
image: node:alpine
user: "1000:1000"
5. Zusammenfassung: Die gehärtete Produktions-Vorlage
version: "3.8"
services:
production-service:
image: alpine:latest
restart: unless-stopped
read_only: true
user: "10001:10001"
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
tmpfs:
- /tmp:rw,noexec,nosuid,size=32m
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"