Zum Inhalt springen
SHIELDMYSERVER

Container ohne root und warum „im Container“ nicht „abgeschottet“ heißt

Was ein Container tatsächlich trennt, welche vier Zeilen in der Compose-Datei den größten Unterschied machen und wann rootless Docker oder Podman die ehrlichere Antwort ist.

hochDiese Woche. Deutlich erhöhtes Risiko, wenn es liegen bleibt.

veröffentlicht 18.08.2026 · 3 min

Titelbild: Container ohne root und warum „im Container“ nicht „abgeschottet“ heißt

Container trennen Namensräume, nicht Vertrauensbereiche. Sie teilen sich einen Kernel, und alles, was diesen Kernel betrifft, betrifft Host und Container gemeinsam. Deshalb ist die Frage „läuft das im Container?“ weniger wichtig als „mit welchen Rechten läuft es dort?“.

Warum hoch

Der Standardfall ist bis heute: Prozess läuft als root im Container, mit deutlich mehr Fähigkeiten als nötig, oft mit einem Volume, das auf Host-Verzeichnisse zeigt. Aus einer Anwendungslücke wird damit im schlimmsten Fall ein Hostzugriff und im Normalfall zumindest Schreibzugriff auf Daten, die den Neustart überleben.

Vier Zeilen mit dem größten Effekt#

services:
  app:
    image: ghcr.io/beispiel/app@sha256:9c4f2b1a...
    user: "10001:10001"          # 1. nicht root
    read_only: true              # 2. Dateisystem nur lesbar
    tmpfs: ["/tmp"]              #    was doch schreiben muss, liegt im RAM
    cap_drop: ["ALL"]            # 3. keine Kernel-Fähigkeiten
    security_opt:
      - no-new-privileges:true   # 4. keine Rechteausweitung über SUID
    networks: [intern]

Die vier Zeilen kosten nichts und schließen die häufigsten Ausbruchswege. Was danach noch fehlt, sind Grenzen für Verbrauch, ein Container ohne Begrenzung kann den Host durch Speicher- oder Prozesshunger lahmlegen:

    pids_limit: 256
    mem_limit: 512m
    cpus: 1.0

Prüfen, was tatsächlich läuft#

# Wer ist Benutzer 0? 
docker ps -q | xargs -I{} docker inspect {} \
  --format '{{.Name}} user=[{{.Config.User}}] priv={{.HostConfig.Privileged}} ro={{.HostConfig.ReadonlyRootfs}}'

# Welche Fähigkeiten sind gesetzt?
docker inspect $(docker ps -q) --format '{{.Name}} {{.HostConfig.CapAdd}} {{.HostConfig.CapDrop}}'

# Wer hat den Docker-Socket eingehängt? (= faktisch root auf dem Host)
docker ps -q | xargs docker inspect --format '{{.Name}}: {{range .Mounts}}{{.Source}} {{end}}' | grep docker.sock
Zwei Dinge, die man nicht tut

--privileged hebt praktisch alle Trennungen auf. Und ein eingehängter /var/run/docker.sock erlaubt jedem Prozess im Container, einen privilegierten Container zu starten, :ro ändert daran nichts, weil der Socket eine Schnittstelle ist und keine Datei mit Rechten. Wo Monitoring das braucht, gehört ein Socket-Proxy davor, der nur Leseaufrufe durchlässt.

Wenn das Image root erzwingt#

Manche Images setzen USER root und schreiben in Systempfade. Zwei saubere Auswege:

# Eigenes Image ableiten und den Benutzer setzen
FROM ghcr.io/beispiel/app:1.8.2
RUN useradd --system --uid 10001 app \
 && chown -R app:app /var/lib/app
USER 10001:10001

Oder, wenn das Image nicht anders kann, rootless betreiben: Bei rootless Docker und bei Podman läuft der gesamte Container-Betrieb unter einem normalen Benutzerkonto. root im Container ist dann ein abgebildeter Benutzer ohne Rechte auf dem Host.

# Podman, ohne Dienst mit Systemrechten
podman run --rm --userns=keep-id -p 127.0.0.1:8080:8080 ghcr.io/beispiel/app:1.8.2
podman generate systemd --new --name app > ~/.config/systemd/user/app.service

Der Preis: Ports unter 1024 sind ohne Zusatzkonfiguration nicht direkt belegbar, und einige Netzwerkkonstrukte verhalten sich anders. Für Anwendungen hinter einem Reverse-Proxy spielt beides keine Rolle.

Netz nicht vergessen#

Die Trennung der Rechte hilft nichts, wenn der Container seine Ports auf allen Adressen veröffentlicht. Warum die Firewall dabei nicht schützt und was stattdessen gilt, steht in Docker umgeht deine Firewall. Für die Datenbank dahinter gilt zusätzlich Die Datenbank gehört nicht ins Netz.

Und das Image selbst#

Ein perfekt eingesperrter Container mit einem anderthalb Jahre alten Image ist kein Fortschritt. Digest festnageln, regelmäßig neu bauen, Bestand kennen: Container-Images aktuell halten und Bestandsliste statt Bauchgefühl.

Was der Container trotzdem nicht leistet#

Es lohnt, die Erwartung zu sortieren. Ein Container mit den Einstellungen oben verhindert, dass aus einer ausgenutzten Anwendung ohne Weiteres ein Hostzugriff wird. Er verhindert nicht, dass die Anwendung selbst ausgenutzt wird, und er schützt die Daten nicht, mit denen sie ohnehin arbeiten darf: Wer die Datenbank abfragen darf, kann sie auslesen, unabhängig davon, unter welcher Nutzernummer der Prozess läuft.

Der zweite blinde Fleck ist der Kernel. Host und Container teilen ihn, also wirkt eine Kernellücke über die Trennung hinweg. Für den Regelbetrieb heißt das schlicht: Der Host muss gepatcht und neu gestartet werden, sonst nützt die beste Container-Konfiguration wenig (Eingespielt heißt nicht wirksam). Wo tatsächlich fremder oder nicht vertrauenswürdiger Code läuft, ist eine virtuelle Maschine die ehrlichere Grenze, mit eigenem Kernel und entsprechendem Aufwand.

Als Faustregel für den Alltag: Container trennen Betriebsbelange sauber und Sicherheitsbereiche nur so weit, wie man sie konfiguriert. Alles, was zwei Vertrauensstufen unterscheidet, Kundendaten gegen Testumgebung, Verwaltung gegen öffentliche Anwendung, gehört auf getrennte Maschinen oder zumindest getrennte Netze.

MRMedia

Geschrieben aus dem laufenden Betrieb: MRMedia betreibt eigene Hosts, Kundendienste und deren Härtung in der EU. Eine veraltete Anleitung ist hier ein Sicherheitsproblem, kein Schönheitsfehler. Korrekturen bitte über Discord.