// Tutorial · Debian · Container

Debian 13 (Trixie): Rootless, Secrets & seccomp – Container im Produktivbetrieb (Teil 6)

📅 07.09.2026 ⏱ 15 Min. Lesezeit ✍️ Redaktion

Teil 5 hat angekündigt, was jetzt kommt: der Container-Daemon ohne Root, echte Secrets statt Umgebungsvariablen und eine zweite Schutzschicht aus seccomp & AppArmor. Teil 6 schließt die Debian-13-Serie mit dem Produktivbetrieb ab – inklusive Podman als rootless Alternative.

Warum Rootless?

In Teil 5 gab es eine Warnung mit Folgen: Wer zur docker-Gruppe gehört, hat faktisch Root-Rechte auf dem Host – die docker.sock ist eine Root-Shell mit Wartezeit. Genau dieses Problem löst Rootless Docker: Der Daemon läuft als normaler Benutzer in einem eigenen User-Namespace. Ein Angreifer, der den Daemon oder einen Container überwindet, landet nicht als Root im Host, sondern als eingeschränkter Benutzer in einem abgekapselten Namespace.

  • Weniger Angriffsfläche: Kein Root-Daemon, keine Root-Socket mehr – die Privilegien-Eskalation über die Docker-Gruppe existiert nicht.
  • Konsequenz aus Teil 5: Secrets via docker inspect sind nur noch für den Besitzer des Rootless-Sockets lesbar, nicht für jeden Host-Benutzer.
  • Passend zur Serie: Der gehärtete Host aus Teil 1–4 bleibt unverändert – der Daemon zieht nur in dein User-Home um.
Voraussetzungen: Ein normaler Benutzer (aus Teil 1), die Pakete aus Teil 2/3 und ein System mit systemd – also genau dein Debian-13-Server. Rootless funktioniert ohne sudo im laufenden Betrieb.

Rootless Docker einrichten

Die Installation läuft in zwei Schritten: erst die Abhängigkeiten, dann das Setup als normaler Benutzer – niemals als root:

sudo apt install dbus-user-session uidmap slirp4netns

# Rootful-Daemon abschalten, damit nur der Rootless-Daemon lauscht
sudo systemctl disable --now docker.service docker.socket
# Jetzt als NORMALER Benutzer ausführen (nicht mit sudo!):
dockerd-rootless-setuptool.sh install

# User-Dienst dauerhaft aktivieren und Linger setzen:
systemctl --user enable --now docker
sudo loginctl enable-linger $USER

uidmap liefert die Sub-UIDs/Sub-GIDs für den User-Namespace, slirp4netns stellt das Netzwerk bereit. enable-linger ist der entscheidende Schritt: Damit läuft deine User-Session auch nach dem Ausloggen weiter – sonst stoppen alle Container beim nächsten SSH-Disconnect.

# Context prüfen und auf rootless umstellen:
docker context ls
docker context use rootless

# Kontrolle – Ausgabe muss rootless zeigen:
docker info | grep -iE "rootless|username|seccomp"
docker context inspect | grep -i host

Der Rootless-Socket liegt danach unter $XDG_RUNTIME_DIR/docker.sock (meist /run/user/1000/docker.sock). Alle Befehle aus Teil 5 – docker compose, docker stats, Watchtower – funktionieren unverändert, sobald der Context aktiv ist.

Merksatz: Der Rootless-Daemon gehört dem Benutzer – nicht dem System. Deshalb heißt die Grundregel: Dienste nie mit sudo docker starten, sondern immer in der eigenen User-Session.

Grenzen & wann Rootful bleiben darf

Rootless ist kein Allheilmittel. Diese Punkte solltest du kennen, bevor du umstellst:

  • Ports unter 1024: Ein unprivilegierter Benutzer darf keine privilegierten Ports binden. Lösung aus der Serie: Der Reverse-Proxy (Artikel nginx/acme.sh) bindet 80/443 und leitet auf die Container-Ports im lokalen Netz weiter.
  • Datei-Rechte auf Volumes: Container laufen im Sub-UID-Bereich des Benutzers. Host-Ordner, die du per Bind-Mount bereitstellst, gehören dem Benutzer – nicht root. Bei Altdaten aus der Rootful-Zeit die Ownership prüfen: sudo chown -R $USER:$USER ./volumes.
  • Spezial-Fälle: Docker-in-Docker, Zugriff auf Kernel-Module oder Geräte (--device), --privileged und manche Netzwerk-Modi funktionieren rootless nicht oder nur eingeschränkt.
  • AppArmor im Container: Das Setzen von AppArmor-Profilen braucht Capabilities, die im User-Namespace fehlen – der Daemon kann Container nicht mit hostweiten Profilen belegen (Details in Abschnitt 5).
Ehrliche Empfehlung: Dienste, die zwingend Kernel-Zugriff brauchen (z. B. manche VPN- oder Monitoring-Lösungen), betreibst du bewusst als Rootful-Ausnahme mit dem Grundgerüst aus Teil 5 – nicht alles andere gleich mit. Mischbetrieb ist erlaubt, solange die Rootful-Socket nicht in Benutzer-Hände wandert.

Docker Secrets statt Umgebungsvariablen

In Teil 5 stand: Env-Variablen kann jeder lesen, der docker inspect darf. Das gilt auch rootless noch innerhalb deiner eigenen Session – und Geheimnisse in Klartext-Variablen landen außerdem in Backups, Logs und Healthchecks. Für echte Secrets stellt Docker Compose /run/secrets bereit: kleine Dateien, die nur im Container gemountet werden.

# 1) Secret-Datei anlegen – nur für deinen Benutzer lesbar
printf 'LangesZufaelligesDBPasswort\n' > ./db_password.txt
chmod 600 ./db_password.txt
# 2) docker-compose.yml
services:
  app:
    image: beispiel/app:latest
    secrets:              # Secret-Liste direkt unter dem Dienst
      - db_password       # Name = Eintrag in der globalen secrets:-Liste
    environment:
      # Apps, die Datei-Pfade unterstützen, bekommen den Pfad als Variable
      - DB_PASSWORD_FILE=/run/secrets/db_password

# 3) Globale Definition am Datei-Ende – hier wird die Quelle festgelegt
secrets:
  db_password:
    file: ./db_password.txt

Die Anwendung liest das Passwort dann aus /run/secrets/db_password – nicht aus einer Umgebungsvariablen. Was nicht in der Variable steht, taucht auch nicht in docker inspect, Prozesslisten oder Crash-Logs auf.

  • Secret-Dateien gehören wie die .env aus Teil 5 nicht ins Git – .gitignore und chmod 600 sind Pflicht.
  • Akzeptiert eine Anwendung nur Env-Variablen, bleibt die .env-Lösung aus Teil 5 die pragmatische Wahl – Dokumentation sagt dann deutlich, wo das Limit liegt.
  • Ein Secret wechseln heißt: Datei ändern und den Stack neu erstellen – docker compose up -d genügt nicht immer, da Datei-Secrets beim Neustart neu gelesen werden.
Backup-Kompatibilität: Die Borg- und Stack-Backups aus Teil 4/5 sichern den Stack-Ordner samt .env und Secret-Dateien. Beim Restore einfach die Rechte (chmod 600) wiederherstellen – mehr ist nicht nötig.
Podman-Hinweis: podman compose – das native Compose-Plugin unter Debian 13 – behandelt die secrets:-Definition identisch zu Docker. Das ältere Python-Tool podman-compose unterstützt Datei-Secrets erst in neueren Versionen zuverlässig; wer darauf angewiesen ist, nutzt besser podman compose oder bleibt bei der .env-Lösung aus Teil 5.

seccomp & AppArmor für Container

Teil 5 hat mit cap_drop und no-new-privileges die Rechte der Prozesse begrenzt. seccomp ergänzt das auf der Syscall-Ebene: Der Kernel blockt einzelne Systemaufrufe, bevor sie den Prozess erreichen. Docker bringt dafür ein gut austariertes Default-Profil mit, das automatisch aktiv ist:

docker info | grep -i seccomp
# Ausgabe: name=seccomp,profile=builtin

Für Dienste mit besonderem Risiko lohnt ein eigenes Profil, das zusätzlich einzelne Aufrufe sperrt – z. B. unshare (Namespace-Eskapaden) in einem Web-Container:

# profile.json – eigene Syscall-Blockliste
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    { "names": ["unshare"], "action": "SCMP_ACT_ERRNO" }
  ]
}
# docker-compose.yml – Profil pro Dienst aktivieren
services:
  web:
    image: nginx:alpine
    security_opt:
      - seccomp=./profile.json

Das eigene Profil startet mit SCMP_ACT_ALLOW (alles erlaubt) und sperrt gezielt – so riskierst du keine kaputten Dienste. Das ausführliche Docker-Default-Profil dient als Referenz für syscalls, die du sicher blocken kannst.

AppArmor: Auf Debian 13 ist AppArmor seit Teil 2 aktiv. Rootful Docker belegt Container automatisch mit dem Profil docker-default; unter Rootless greift es – wie in Abschnitt 3 beschrieben – nicht in den Container hinein. Dort übernehmen seccomp, User-Namespace und no-new-privileges die Schutzfunktion. Auf einem Rootful-Ausnahme-Host (Abschnitt 3) lohnt ein eigenes Profil:

# /etc/apparmor.d/docker.nginx
abi <abi/3.0>,
#include <tunables/global>
profile docker.nginx flags=(attach_disconnected) {
  #include <abstractions/base>
  network inet tcp,
  /etc/nginx/** r,
  /var/log/nginx/** w,
  /var/cache/nginx/** w,
  deny /etc/shadow rw,
}
# Profil laden und dem Container zuweisen
sudo apparmor_parser -r /etc/apparmor.d/docker.nginx
sudo aa-status | grep nginx

# docker-compose.yml:
#   security_opt:
#     - apparmor=docker.nginx
Merksatz: Drei Ebenen greifen ineinander: Capabilities weg (Teil 5), unnötige Syscalls blocken (seccomp), Dateisystem-Zugriffe einschränken (AppArmor). Rootless ersetzt die dritte Ebene durch den User-Namespace.

Podman: die rootless Alternative

Podman ist daemonless und rootless von Haus aus – der Blog-Artikel „Podman vs. Docker“ hat den Vergleich geliefert, hier kommt der Betrieb. Die Container-Kommandos sind weitgehend identisch, für Stacks gibt es die Compose-Unterstützung von Podman:

# Als normaler Benutzer – kein Daemon nötig
sudo apt install podman podman-compose

# Compose-Stacks aus Teil 5 laufen lassen:
podman compose -f /opt/stacks/app/docker-compose.yml up -d

Der elegante Unterschied ist Quadlet: Podman übersetzt Container-Definitionen direkt in systemd-User-Units – dein Dienst wird damit ein ganz normaler, überwachbarer systemd-Dienst:

# ~/.config/containers/systemd/app.container
[Unit]
Description=Beispiel-Dienst

[Container]
Image=docker.io/library/nginx:1.27-alpine
PublishPort=127.0.0.1:8080:80

[Service]
Restart=always

[Install]
WantedBy=default.target
# Unit aktivieren – systemd übernimmt Start, Restart und Autostart
systemctl --user daemon-reload
systemctl --user enable --now app
systemctl --user status app
  • Migration: docker-Alias auf podman setzen – die meisten Befehle aus Teil 5 laufen unverändert durch.
  • Kein Daemon: Kein zentraler Prozess mit Root-Socket, jeder Dienst gehört seiner eigenen User-Unit.
  • Für Einsteiger: Wer das Ökosystem aus Teil 5 (Watchtower, Compose) weiternutzen will, bleibt bei Rootless Docker – Podman ist die Wahl, wenn du systemd als Kontrollinstanz willst.

Häufige Probleme

ProblemLösung
Container laufen nach Reboot nicht mehrsudo loginctl enable-linger $USER setzen und systemctl --user enable docker prüfen.
docker zeigt wieder den Rootful-Daemondocker context ls prüfen und docker context use rootless setzen – nie sudo docker verwenden.
Port 80/443 lässt sich nicht bindenUnprivilegierte Benutzer dürfen keine Ports unter 1024 binden – Reverse-Proxy übernimmt 80/443 und leitet auf Container-Ports weiter.
Volumes gehören plötzlich falschen BesitzernRootless-Container laufen im Sub-UID-Bereich – Ownership der Host-Ordner auf deinen Benutzer setzen (chown -R $USER:$USER).
AppArmor-Profil wird nicht angewendetRootless kann keine hostweiten Profile setzen – seccomp + User-Namespace übernehmen; AppArmor nur auf Rootful-Ausnahme-Hosts.
Backup-Skripte aus Teil 4/5 finden den Daemon nichtDer Root-Timer aus Teil 4 läuft im System-Kontext und sieht den Rootless-Socket von lena nicht – docker compose stop stoppt dann nichts (Backup bei laufendem Container = riskant) oder bricht ab. Deshalb die Variable im Skript vor dem Compose-Befehl setzen: export DOCKER_HOST="unix:///run/user/1000/docker.sock" (UID bei Bedarf anpassen). Alternativ läuft das Backup als systemd-User-Timer in der Session von lena – dann entfällt die Variable.
Secret-Datei wird nicht gemountetPfad unter secrets: prüfen und Stack neu erstellen – Datei-Secrets werden beim Neustart gelesen.

Fazit & Serien-Ende

Damit ist die Debian-13-Serie komplett: installieren (Teil 1), härten (Teil 2), abschotten (Teil 3), überwachen und sichern (Teil 4), Container betreiben (Teil 5) – und jetzt den Container-Betrieb auf das Niveau des Hosts heben. Rootless nimmt dem Daemon die Krone, Secrets und seccomp schließen die Lücken, die in Teil 5 bewusst offen blieben. Wer mag, wechselt mit Podman den Motor, ohne die Konzepte zu verlernen.

Die 4 Merksätze aus Teil 6: ① Rootless ist der Standard – Rootful nur als begründete Ausnahme ② Geheimnisse gehören nach /run/secrets, nicht in Variablen ③ seccomp blockt Syscalls, AppArmor blockt Dateizugriffe – beide ergänzen Teil 5 ④ Linger setzen, Context prüfen, Restore testen: Der Betrieb ist erst mit Kontrolle fertig

Weiterlesen: Die einzelnen Bausteine – Watchtower, Uptime Kuma, Podman vs. Docker, Portainer und Dockge – sind als eigene Artikel auf dem Blog vertieft. Die Serie endet hier, die Dienste-Liste wächst weiter.

📝
....

Wir betreiben unsere komplette Infrastruktur selbst auf Open-Source-Software und schreiben nur über Tools, die wir im Alltag wirklich einsetzen. Fragen zum Artikel? Schreib uns.