Design wechseln

Docker-Sicherheitskonfiguration: Vollständiger Leitfaden zum Betrieb ohne root

Easton editorial illustration: environment switchboard

Letzten Monat habe ich einem Freund bei der Container-Konfiguration seines Unternehmens geholfen – ein kurzer docker inspect zeigte: alles lief als root, teils mit --privileged. Zwei Wochen später wurde ein Container kompromittiert; der Angreifer nutzte Container-Escape und übernahm den Host.

Das ist keine Panikmache. CVE-2024-21626 aus Januar 2024 ist ein typisches Beispiel: Angreifer mussten nur den „Working Directory“-Parameter des Containers kontrollieren, um über ein geleaktes File Descriptor das gesamte Host-Dateisystem zu manipulieren. Noch alarmierender sind Daten der NSFOCUS-Forschung: 76 % der Images auf Docker Hub enthalten Sicherheitslücken, 67 % davon sind hochkritisch.

Früher habe ich das kaum beachtet – im Dockerfile einfach FROM ubuntu, dann RUN apt-get install. Schließlich läuft alles im Container, die Isolation sollte reichen, oder? Bis ein Container in der Lasttest-Umgebung kompromittiert wurde und in den Logs der Befehl zum Mounten der Host-Festplatte auftauchte – da wurde mir klar, wie ernst das ist.

Container von root auf Nicht-root umzustellen ist weniger kompliziert, als viele denken. Heute geht es um: Warum ist root als Standard so gefährlich? Wie legt man Nicht-root-Benutzer im Dockerfile an? Wie nutzt man --user? Und wie konfiguriert man Capabilities und AppArmor? Nach diesem Artikel können Sie das Sicherheitsrisiko Ihrer Produktions-Container um bis zu 80 % senken.

76%
Images mit Schwachstellen
Docker Hub-Statistik
67%
Hochkritische Lücken
NSFOCUS-Forschung
80%
Risikoreduktion
Nicht-root-Konfiguration
Source: NSFOCUS-Forschungsbericht

Warum sollte man Container nicht als root betreiben?

Container-Escape: vom Sandbox zum Host in einem Schritt

Viele halten Container für eine Sandbox – was drin passiert, betrifft den Host nicht. In Wirklichkeit basiert die Isolation auf Linux-Namespaces und cgroups, nicht auf hardwarenaher VM-Isolation. Bei Fehlkonfiguration oder Kernel-Bugs bricht diese Schicht schnell zusammen.

CVE-2024-21626 ist ein eindrückliches Beispiel. Angreifer entdeckten, dass runc (Docker-Runtime) beim Verarbeiten des Working Directory ein File Descriptor auf das Host-Dateisystem leakt. Konkret konnten Angreifer über diese Schwachstelle beliebige Host-Dateien lesen und schreiben – sogar kritische Binaries wie /usr/bin/bash überschreiben. Stellen Sie sich vor: Ihre Web-App wird kompromittiert, und der Angreifer ersetzt alle Container auf dem Host durch Mining-Malware – das ist kein Szenario, sondern ist bereits passiert.

Ein häufiger Angriffsweg ist der --privileged-Modus. Dieser Parameter bedeutet praktisch: „Gib diesem Container alle Host-Rechte.“ Der root-Benutzer im Container erhält damit nahezu volle Host-Fähigkeiten: Geräte mounten, Kernel-Module laden, Netzwerk konfigurieren … NSFOCUS analysierte einen Fall, in dem Angreifer über einen privilegierten Container mit mount /dev/sda1 /mnt die Host-Festplatte einhingen und per Cron-Job Daten exfiltrierten – in weniger als zehn Minuten.

Ein besonders heimtückisches Risiko: das Mounten des Docker-Sockets. Wer im Container Docker-Befehle ausführen will, hängt oft /var/run/docker.sock ein – und übergibt damit die Kontrolle über alle Host-Container. Nach Kompromittierung lässt sich ein neuer privilegierter Container erzeugen und der Host übernommen. Tencent Cloud Security dokumentierte solche Angriffsketten.

Warum root der größte Sicherheitsbruch ist

Der Kern des Problems: Die root-UID 0 im Container und die root-UID 0 auf dem Host sind derselbe Benutzer.

Namespaces isolieren – ja, aber der UID-Namespace ist standardmäßig deaktiviert (Kompatibilität). Läuft ein Prozess im Container als root und bricht ein Namespace oder tritt ein Kernel-Bug auf, hat der Prozess auf dem Host root-Rechte. In Tests mit SYS_ADMIN in einem Container konnte ich als root procfs des Hosts mounten und über /proc/sys/kernel/core_pattern eine Reverse Shell schreiben – Host-root war erreicht, einfacher als erwartet.

Alibaba Cloud nennt fünf Hauptursachen für Container-Escape: Kernel-Bugs, Fehlkonfiguration, unsichere Images, Rechte-Missbrauch, unsichere Container-zu-Container-Kommunikation. Vier davon hängen direkt mit root-Rechten zusammen. Nicht-root-Betrieb senkt mindestens drei dieser Risiken deutlich.

Typisches Szenario: Node.js-Apps lauschen gern auf Port 80, aber Ports unter 1024 erfordern root. Viele starten die App deshalb als root. Hat Express eine Path-Traversal-Schwachstelle, liest der Angreifer /etc/passwd und versucht SSH auf den Host – ohne Container-Escape.

Klingt beunruhigend? Die Lösung ist überschaubar: Prinzip der minimalen Rechte. Geben Sie nur die Rechte, die die Anwendung braucht – nicht pauschal root.

Nicht-root-Benutzer konfigurieren: beginnend im Dockerfile

Sichere Docker-Container-Konfiguration mit Nicht-root-Benutzer

Vollständiger Leitfaden von der Benutzeranlage im Dockerfile bis zu Laufzeit-Sicherheitsparametern – Containersicherheitsrisiko um bis zu 80 % senken

Estimated time: PT30M

  1. 1

    Step 1: Nicht-root-Benutzer im Dockerfile anlegen

    Dedizierten Benutzer und Gruppe anlegen:
  2. 2

    Step 2: Port-Binding lösen

    Port-Binding lösen:
  3. 3

    Step 3: Laufzeit-Sicherheitsparameter

    Laufzeit-Sicherheitsparameter:
  4. 4

    Step 4: Granulare Capabilities-Kontrolle

    Granulare Capabilities-Kontrolle:
  5. 5

    Step 5: AppArmor/SELinux aktivieren

    Debian/Ubuntu: AppArmor (docker-default profile), RHEL/CentOS: SELinux. Standard-Profile sind bereits streng – meist keine Anpassung nötig.
  6. 6

    Step 6: Image-Scanning und Monitoring

    Trivy oder Docker scan für regelmäßige Schwachstellen-Scans; hochkritische Lücken vor Go-Live beheben. Abnormale Restarts und Ressourcennutzung überwachen, Konfiguration regelmäßig auditieren.

Nicht-root-Benutzer korrekt anlegen

Standardvorgehen:

FROM node:18-alpine

# Dedizierten Benutzer und Gruppe anlegen (UID/GID festlegen)
RUN addgroup -g 5000 appgroup \
    && adduser -D -u 5000 -G appgroup appuser

# Arbeitsverzeichnis setzen
WORKDIR /app

# Dateien kopieren und Besitzer setzen (wichtiger Schritt!)
COPY --chown=appuser:appgroup package*.json ./
RUN npm install
COPY --chown=appuser:appgroup . .

# Zu Nicht-root wechseln (alle folgenden Befehle laufen als appuser)
USER appuser

# Anwendung starten
CMD ["node", "server.js"]

Jede Zeile hat Bedeutung.

Warum UID und GID festlegen? Ohne feste Zahlen vergibt das System automatisch – verschiedene Container können unterschiedliche UIDs haben; gemountete Dateien sind dann zwischen Containern nicht konsistent lesbar. Feste UID (z. B. 5000) vereinheitlicht Berechtigungen.

Was bringt COPY --chown? Bei normalem COPY plus RUN chown entstehen zwei Layer: Kopie als root, dann chown. --chown setzt den Besitzer direkt beim Kopieren – weniger Speicher, sicherer. Vergessenes chown führte bei mir einmal zu „Permission denied“ beim Start – eine halbe Stunde Debugging.

Die Position von USER ist entscheidend. Davor laufen Befehle als root (z. B. RUN npm install schreibt nach /app), danach als appuser. USER zu früh platziert → Installationsbefehle schlagen fehl. Regel: root-relevante Schritte vor USER.

Häufige Fallstricke und Lösungen

Fallstrick 1: Port-Binding

Nach Umstellung auf Nicht-root: Error: listen EACCES: permission denied 0.0.0.0:80 – Ports unter 1024 sind privilegiert.

Lösungen:

  1. Hohen Port nutzen (empfohlen): Anwendung auf 3000 oder 8080, Reverse Proxy via Nginx oder Load Balancer
  2. NET_BIND_SERVICE Capability: ermöglicht Nicht-root-Binding niedriger Ports
# Anwendung lauscht intern auf Port 3000
EXPOSE 3000
USER appuser
CMD ["node", "server.js"]  # intern Port 3000

Mapping in docker-compose oder K8s:

ports:
  - "80:3000"  # Host 80 → Container 3000

Fallstrick 2: Logs und temporäre Dateien

Ein Python-Container als Nicht-root schlug fehl, weil die App nach /var/log schreiben wollte – appuser hatte keine Rechte.

# Log-Verzeichnis anlegen und Berechtigungen setzen
RUN mkdir -p /var/log/myapp && \
    chown -R appuser:appgroup /var/log/myapp

USER appuser

Besser: stdout/stderr nutzen, Logs von Docker oder K8s sammeln lassen:

# Keine Datei-Logs
logging.basicConfig(stream=sys.stdout, level=logging.INFO)

Fallstrick 3: Volume-Berechtigungen

Host-Verzeichnis /data gehört root, im Container kann appuser (UID 5000) nicht lesen/schreiben.

# Fehlerhaftes Beispiel
docker run -v /data:/app/data myapp
# appuser kann /app/data nicht nutzen

Zwei Lösungen:

# Option 1: Berechtigungen auf dem Host setzen
sudo chown -R 5000:5000 /data

# Option 2: Benanntes Volume (Docker verwaltet Rechte)
docker volume create appdata
docker run -v appdata:/app/data myapp

Laufzeit-Sicherheitsparameter: —user und mehr

—user überschreibt Image-Einstellungen

Third-Party-Images ohne USER laufen oft als root. Neubau zu aufwendig? --user setzt den Benutzer zur Laufzeit:

# Variante 1: UID:GID direkt
docker run --user=1001:1001 nginx:latest

# Variante 2: aktueller Host-Benutzer (Entwicklung)
docker run --user="$(id -u):$(id -g)" -v "$PWD:/app" node:18 npm test

# Variante 3: bekannter Benutzername (muss im Image existieren)
docker run --user=nobody redis:alpine

Variante 2 ist in der lokalen Entwicklung praktisch: generierte Dateien haben auf dem Host die richtigen Rechte, kein sudo zum Löschen.

Achtung: --user überschreibt USER im Dockerfile. Ist das Image bereits Nicht-root, setzt --user=0:0 wieder root – dann nutzen Sie den Schutz nicht. Immer die Image-Defaults prüfen.

Schreibgeschütztes Dateisystem: Angreifer können nichts persistieren

Angreifer wollen Malware oder Konfiguration ändern – bei read-only scheitert ein Großteil davon.

# Einfachste read-only-Konfiguration
docker run -d --read-only nginx:alpine

# Temporäre Schreibzugriffe nötig?
docker run -d \
  --read-only \
  --tmpfs /tmp \
  --tmpfs /var/run \
  nginx:alpine

--tmpfs mountet RAM-Dateisysteme – Inhalt verschwindet nach Container-Neustart, ideal für Temp-Dateien. Ein API-Service mit Logs auf stdout, Sessions in Redis und ohne persistente Schreibzugriffe: read-only macht post-exploitation deutlich schwerer.

Privilege Escalation verbieten: no-new-privileges

Verhindert, dass Prozesse im Container via setuid/setgid Rechte erhöhen. Selbst mit SUID auf /bin/su gibt es kein Upgrade auf root.

docker run --security-opt=no-new-privileges myapp

Praxistest: sudo in einem Container mit dieser Option schlägt mit „effective uid is not 0“ fehl – wirksam gegen Privilege-Escalation.

Produktionskonfiguration: Kombination

docker run -d \
  --name secure-webapp \
  --user=5000:5000 \         # Nicht-root
  --read-only \               # schreibgeschütztes Root-FS
  --tmpfs /tmp:size=64M \     # 64 MB Temp
  --security-opt=no-new-privileges \  # keine Privilege Escalation
  --cap-drop=ALL \            # alle Capabilities entfernen
  --cap-add=NET_BIND_SERVICE \ # nur Port-Binding falls nötig
  -p 443:8443 \               # Port-Mapping
  -v appdata:/app/data \      # einziger Schreibort
  --memory=512m \             # Speicherlimit
  --cpus=1.0 \                # CPU-Limit
  myapp:1.0.0

Jeder Parameter hat einen Zweck. Mehrere Kernservices laufen bei mir seit über zwei Jahren so ohne Sicherheitsvorfall. Debugging ist etwas umständlicher – kein exec zum Live-Ändern von Dateien – aber der Trade-off lohnt sich.

In K8s entsprechend im SecurityContext:

securityContext:
  runAsNonRoot: true
  runAsUser: 5000
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop: ["ALL"]
    add: ["NET_BIND_SERVICE"]

Granulare Rechte: Capabilities-Mechanismus

Was sind Capabilities? Warum sicherer als root?

Klassische Linux-Rechte sind binär: root darf alles, Normalbenutzer wenig. Capabilities teilen root-Superrechte in über 40 Einzelfähigkeiten – Sie vergeben nur die nötigen.

Analogie: root ist ein Generalschlüssel für alle Räume; Capabilities sind Einzelschlüssel. Kompromittierte Schlüssel öffnen nur autorisierte Bereiche, nicht das Rechenzentrum.

Docker behält standardmäßig 14 Capabilities, u. a.:

  • CHOWN: Dateibesitzer ändern
  • NET_BIND_SERVICE: Ports unter 1024 binden
  • SETUID/SETGID: Benutzer-/Gruppen-ID ändern
  • KILL: Signale an andere Prozesse
  • DAC_OVERRIDE: Dateizugriffsprüfungen umgehen

Für viele Apps reicht das – einige Capabilities sind dennoch gefährlich und sollten entfernt werden.

Gefährliche Capabilities (niemals vergeben!)

SYS_ADMIN – fast halbes root

Erlaubt mount, Namespace-Änderungen, Kernel-Module laden … Ein Container mit SYS_ADMIN: Angreifer nutzen unshare, mounten Host-Festplatten – Spiel vorbei.

# So nicht!
docker run --cap-add=SYS_ADMIN myapp  # ❌ gefährlich!

NET_ADMIN – Netzwerksteuerung

Routing, Firewall, Sniffing – nur für Netzwerk-Tools (VPN, Soft-Router) sinnvoll.

SYS_MODULE – Kernel-Module laden

Code direkt in den Kernel – offensichtlich kritisch.

Minimale Rechte in der Praxis

Strategie 1: Alles entfernen, gezielt hinzufügen (empfohlen)

docker run -d \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --cap-add=CHOWN \
  myapp

Häufigste Strategie. Fehlt eine Capability, Fehlermeldung lesen und gezielt ergänzen – z. B. setuid()--cap-add=SETUID.

Strategie 2: Nur Gefährliches entfernen (schnelle Härtung)

docker run -d \
  --cap-drop=SYS_ADMIN \
  --cap-drop=NET_ADMIN \
  --cap-drop=SYS_MODULE \
  --cap-drop=SYS_RAWIO \
  myapp

Wenn unklar ist, welche Capabilities nötig sind, aber offensichtlich Gefährliches weg soll.

Welche Capabilities braucht die Anwendung?

Methode 1: Trial-and-Error

# Schritt 1: drop all
docker run --cap-drop=ALL myapp
# Fehler: bind: permission denied

# Schritt 2: NET_BIND_SERVICE hinzufügen
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp
# Start erfolgreich

Methode 2: capsh

$ docker run --rm -it --cap-drop=ALL ubuntu capsh --print
Current: =
# leer

$ docker run --rm -it ubuntu capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,...
# Standard-14 Capabilities

Methode 3: Referenztabelle

AnwendungstypErforderliche CapabilitiesHinweis
Web-App (hoher Port)keine3000+ ohne Sonderrechte
Web-App (niedriger Port)NET_BIND_SERVICE80/443
Datenbank (MySQL/Postgres)keineStandard-Ports hoch
Nginx/CaddyNET_BIND_SERVICEbei direktem 80/443
VPN/Netzwerk-ToolNET_ADMINRouting/Interfaces

Die meisten Business-Apps laufen mit drop all, oft plus NET_BIND_SERVICE.

Mandatory Access Control: AppArmor und SELinux

Kurz erklärt

Capabilities steuern, was ein Prozess tun darf; AppArmor/SELinux steuern, auf welche Dateien und Ressourcen zugegriffen werden darf – Mandatory Access Control (MAC) auf OS-Ebene. Selbst root muss Profile-Regeln einhalten.

Analogie: Sie sind Chef (root), betreten den Serverraum aber nur mit Berechtigungskarte. AppArmor/SELinux ist das Zugangssystem.

Systemwahl:

  • Debian/Ubuntu: AppArmor (Standard)
  • RHEL/CentOS: SELinux (Standard)
  • Nicht beides gleichzeitig aktivieren

AppArmor: einfach und ausreichend (Einstieg)

Docker wendet automatisch docker-default an – bereits streng. Meist keine Extra-Konfiguration nötig.

# AppArmor-Profil des Containers prüfen
docker inspect mycontainer | grep -i apparmor
# "AppArmorProfile": "docker-default"

Was macht docker-default?

Container dürfen u. a. nicht:

  • Dateisysteme mounten
  • Kernel-Parameter unter /proc/sys/ ändern
  • sensible Host-Geräte unter /dev/ nutzen
  • AppArmor-Konfiguration ändern

Test: Selbst als root schlägt mount /dev/sda1 /mnt in einem AppArmor-geschützten Container mit „Permission denied“ fehl. Capabilities plus AppArmor erhöhen die Escape-Schwierigkeit deutlich.

SELinux: mächtiger, komplexer

Jede Datei und jeder Prozess erhält Labels; Policies definieren erlaubte Zugriffe.

# SELinux-Label des Container-Prozesses
docker inspect mycontainer | grep -i selinux
# "ProcessLabel": "system_u:system_r:container_t:s0:c123,c456"

c123,c456 sind Kategorien – pro Container eindeutig, Container A kann Container B nicht lesen.

SELinux-Konfiguration ist anspruchsvoll, Fehlermeldungen oft kryptisch. Auf Ubuntu reicht AppArmor; auf RHEL schützt SELinux standardmäßig – meist ohne Anpassung.

Praxis: welches System, wann?

Entwicklung

  • Temporär deaktivieren (apparmor=unconfined oder label=disable) zum Debuggen
  • Frage: Wird in Produktion wieder aktiviert?

Test

  • Immer an, Standard-Profile
  • Konflikte zwischen Sicherheit und App-Funktion früh finden

Produktion

  • Immer an, keine Kompromisse
  • Standard-Profile, Custom nur mit guter Begründung
  • Logs auf DENIED-Ereignisse prüfen

Erfahrung: 99 % der DENIED sind korrekt – Angriff oder schlechtes App-Design. Rechte lockern ist selten nötig.

Vollständige Sicherheits-Checkliste

Alles zusammengefasst – wer diese Liste abarbeitet, liegt bei Containersicherheit vorn.

Image-Build

Dockerfile-Checks:

  • ✅ Offizielle oder vertrauenswürdige Base-Images
  • ✅ Version pinnen (node:18.17-alpine, nicht node:latest)
  • ✅ Dedizierter Nicht-root-Benutzer mit fest UID/GID
  • COPY --chown für Dateibesitzer
  • USER nach Installation, vor Start
  • ✅ Hohe Ports (3000+) oder passende Capabilities
  • ✅ Multi-Stage-Build für kleinere Angriffsfläche
  • ✅ Keine Secrets im Image (Umgebungsvariablen/Secrets nutzen)

Image-Scanning

Pflicht-Scans:

# Docker scan (Snyk)
docker scan myapp:latest

# Trivy (schneller, umfassender – empfohlen)
trivy image myapp:latest

# Clair (CI/CD-Integration)
# Harbor mit Auto-Scan

76 % der Docker-Hub-Images haben Lücken – regelmäßiges Scannen ist Pflicht. Unser Team:

  • Hochkritisch: Fix vor Go-Live
  • Mittel: Risikoabschätzung und Monitoring
  • Niedrig: dokumentieren, regelmäßig reviewen

Laufzeit-Konfiguration

docker-compose Produktionsvorlage:

services:
  myapp:
    image: myapp:1.0.0
    user: "5000:5000"           # Nicht-root
    read_only: true             # read-only Root-FS
    tmpfs:
      - /tmp:size=64M           # Temp im RAM
    security_opt:
      - no-new-privileges:true  # keine Privilege Escalation
      - apparmor=docker-default # AppArmor
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE        # nur wenn nötig
    volumes:
      - appdata:/app/data:rw    # Schreibzugriff explizit
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
    ports:
      - "8080:8080"

Produktions-Betrieb

Monitoring:

  • ✅ Abnormale Container-Restarts (Angriff/Crash)
  • ✅ Ressourcen-Spitzen (Mining)
  • ✅ Audit-Logs für Container-Operationen

Regelmäßige Audits:

  • ✅ Laufende Images monatlich scannen
  • --privileged und gefährliche Capabilities finden
  • ✅ Netzwerk-Policies und exponierte Ports prüfen

Häufige Fragen und Lösungen

Q1: Permission-Fehler nach Umstellung auf Nicht-root?

Diagnose:

  1. Fehlermeldung genau lesen (Datei vs. Port?)
  2. Datei: --chown und Verzeichnisrechte im Dockerfile prüfen
  3. Port: hohen Port oder NET_BIND_SERVICE

Typische Fehler:

# Fehler: Error: EACCES: permission denied, open '/app/logs/app.log'
# Ursache: Log-Verzeichnis ohne Schreibrecht für appuser
# Fix:
RUN mkdir -p /app/logs && chown appuser:appgroup /app/logs

# Fehler: Error: listen EACCES: permission denied 0.0.0.0:80
# Ursache: Nicht-root kann niedrige Ports nicht binden
# Fix 1: Port 3000 + Mapping
EXPOSE 3000
# Fix 2: Capability
docker run --cap-add=NET_BIND_SERVICE myapp

Q2: Volume-Berechtigungen passen nicht?

Häufigstes Problem – drei Lösungen:

Option 1: UID/GID auf dem Host (empfohlen)

sudo chown -R 5000:5000 /data
docker run -v /data:/app/data myapp

Option 2: Benanntes Volume

docker volume create --opt o=uid=5000,gid=5000 appdata
docker run -v appdata:/app/data myapp

Q3: Wann braucht man wirklich root? Fast nie!

SzenarioAlternative ohne root
Port 80/443NET_BIND_SERVICE oder hoher Port + Load Balancer
SystempaketeIm Dockerfile vor USER installieren, nicht zur Laufzeit
SystemkonfigurationUmgebungsvariablen/Konfig-Dateien, nicht Runtime-Änderungen
Docker SocketExtrem gefährlich – Docker API oder K8s API statt Socket-Mount

Einzig sinnvoller root-Fall, den ich sah: Legacy-Migrations-Tool, hardcodiert root, Code nicht änderbar – isoliert in Einmal-Container, danach zerstören.

Q4: Third-Party-Image läuft als root?

Priorität:

  1. Offizielle Nicht-root-Variante (-rootless, -nonroot)
  2. --user zur Laufzeit
docker run --user=65534:65534 third-party-image  # nobody
  1. Eigenes Dockerfile mit USER
FROM third-party-image:latest
RUN adduser -D -u 5000 appuser
USER appuser
  1. Maintainer um Nicht-root-Image bitten

Fazit

Kernbotschaft: Container als root = Hintertür für Angreifer.

Kurz zusammengefasst:

  • Container-Escape ist real – CVE-2024-21626, privilegierte Mounts
  • USER im Dockerfile und --user zur Laufzeit: geringer Aufwand, großer Nutzen
  • Capabilities: drop all, gezielt hinzufügen
  • read-only, no-new-privileges, AppArmor: Defense in Depth
  • 76 % der Images mit Lücken – Scannen ist Pflicht

Drei Schritte ab heute:

  1. Dockerfile prüfen – fehlt USER, ergänzen
  2. Produktion auditieren--privileged und root-Container identifizieren und härten
  3. Scan-Pipeline – Sicherheit in CI/CD verankern

Sicherheit ist kein Einmalprojekt, sondern Prozess. Der erste Schritt von root zu Nicht-root setzt Sie bereits vor vielen anderen. Warten Sie nicht auf den Vorfall – die Lektion meines Freundes ist greifbar.

FAQ

Warum sollte man Docker-Container nicht als root betreiben?
Die root-UID 0 im Container und die root-UID 0 auf dem Host sind derselbe Benutzer – der UID-Namespace ist standardmäßig deaktiviert. Tritt ein Kernel-Bug oder ein Konfigurationsfehler auf, der den Namespace ungültig macht, hat der root-Prozess im Container auch auf dem Host root-Rechte.

Container-Escape-Schwachstellen wie CVE-2024-21626 zeigen: Container-Isolation ist keine absolute Sicherheit. 76 % der Images auf Docker Hub enthalten Sicherheitslücken, 67 % davon sind hochkritisch. Mit einem Nicht-root-Benutzer lässt sich das Containersicherheitsrisiko um bis zu 80 % senken.
Wie konfiguriert man einen Nicht-root-Benutzer im Dockerfile?
Dedizierten Benutzer und Benutzergruppe anlegen (UID/GID festlegen, z. B. 5000), Dateibesitzer mit COPY --chown=appuser:appgroup setzen, USER-Anweisung nach Installationsbefehlen und vor dem Startbefehl platzieren.

Alle root-relevanten Operationen vor USER ausführen. Feste UID/GID vermeiden Berechtigungsprobleme beim Mounten von Datenvolumes.
Was tun, wenn Nicht-root-Benutzer Port 80/443 nicht binden können?
Zwei Ansätze:

1) Hohen Port verwenden (empfohlen):
• Anwendung lauscht auf 3000 oder 8080
• Nginx oder Load Balancer als Reverse Proxy
• Port-Mapping: Host-Port 80 auf Container-Port 3000

2) NET_BIND_SERVICE Capability nutzen:
• Ermöglicht Nicht-root-Benutzern das Binden niedriger Ports
• Vermeidet root für direktes Lauschen auf 80/443
Was sind Capabilities? Wie konfiguriert man minimale Rechte?
Capabilities zerlegen root-Superrechte in über 40 einzelne Fähigkeiten – Sie geben einem Prozess nur die benötigten.

Best Practices:
• Zuerst --cap-drop=ALL, um alle Capabilities zu entfernen
• Dann gezielt hinzufügen (z. B. NET_BIND_SERVICE für Port-Binding)
• Gefährliche Capabilities wie SYS_ADMIN, NET_ADMIN, SYS_MODULE niemals vergeben

Die meisten Business-Anwendungen laufen mit drop all; höchstens NET_BIND_SERVICE wird zusätzlich benötigt.
Welche Sicherheitsparameter sollte man in der Produktion setzen?
Produktionsreife Sicherheitskonfiguration:
• --user=5000:5000 für Nicht-root-Benutzer
• --read-only für schreibgeschütztes Dateisystem, --tmpfs für temporäre Verzeichnisse
• --security-opt=no-new-privileges verbietet Privilege Escalation
• --cap-drop=ALL entfernt alle Capabilities, dann gezielt hinzufügen
• AppArmor/SELinux für Mandatory Access Control aktivieren

K8s-Nutzer können dieselbe Strategie im SecurityContext des Pods konfigurieren.
Was tun bei Berechtigungskonflikten nach Volume-Mount?
Drei Lösungen:

1) UID/GID auf dem Host vorab setzen (empfohlen):
• sudo chown -R 5000:5000 /data
• UID muss mit Container-Benutzer übereinstimmen

2) Benanntes Volume mit Docker-verwalteten Rechten:
• docker volume create --opt o=uid=5000,gid=5000 appdata

3) Im Dockerfile Verzeichnis für Anwendungsbenutzer anlegen und Berechtigungen setzen
Wie ermittelt man, welche Capabilities eine Anwendung benötigt?
Drei Methoden:

1) Trial-and-Error:
• Zuerst --cap-drop=ALL und Fehlermeldungen prüfen
• Capabilities anhand der Fehler gezielt hinzufügen

2) capsh-Tool nutzen:
• docker run --rm -it ubuntu capsh --print zeigt aktuelle Capabilities

3) Typische Anforderungen als Referenz:
• Web-Apps (hohe Ports): keine speziellen Rechte
• Web-Apps (niedrige Ports): NET_BIND_SERVICE
• VPN/Netzwerk-Tools: NET_ADMIN

12 Min. Lesezeit · Veröffentlicht am: 18. Dez. 2025 · Aktualisiert am: 14. Juli 2026

Ähnliche Beiträge

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog