Zum Inhalt

Containerisierung & Systemhärtung mit Podman

Erstellt: 05.11.2026  ·  Zuletzt aktualisiert: 06.11.2026  ·  Nejat Hakan

Offizielles Podman-Logo

Podman (Pod Manager) ist eine moderne, daemonlose Open-Source-Container-Engine für Linux zur Entwicklung, Verwaltung und Ausführung von OCI-Containern (Open Container Initiative) und Kubernetes-kompatiblen Pods.

Unter Beibehaltung vollständiger CLI-Kompatibilität zu Docker (alias docker=podman) definiert Podman die Containerausführung von Grund auf neu: Es eliminiert den zentralen, mit Root-Rechten laufenden Hintergrunddienst und führt Container nativ als unprivilegierte Benutzerprozesse (rootless) direkt integriert in systemd aus.


1. Architektur: Warum Daemonless & Rootless entscheidend sind

Herkömmliche Container-Engines stützen sich auf einen monolithischen Hintergrund-Daemon (dockerd), der mit vollständigen root-Rechten läuft. Stürzt dieser Dienst ab, hängen alle untergeordneten Container; gelingt einem Angreifer der Ausbruch aus einem Container oder die Ausnutzung einer Sicherheitslücke im Daemon, erlangt er unmittelbar die vollständige Kontrolle über den Host-Server.

Podman ersetzt dieses Client-Server-Daemon-Modell durch die klassische UNIX-Fork/Exec-Architektur:

Architektur 1: Traditionelle Daemon-Architektur (Docker)

%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TD
    D_CLI["docker CLI"] ==>|"REST-API über Socket"| D_Daemon["🔴 dockerd Daemon<br/>(Läuft als root)"]
    D_Daemon --> D_ContD["containerd"]
    D_ContD --> D_Runc1["runc → Container A"]
    D_ContD --> D_Runc2["runc → Container B"]

Architektur 2: Podman Fork/Exec-Architektur (Rootless)

%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TD
    P_User["🟢 Unprivilegierter User<br/>(UID 1000)"] ==>|"Direktes fork/exec"| P_CLI["podman CLI"]
    P_CLI --> P_Crun["crun / runc (OCI Runtime)"]
    P_Crun --> P_Conmon1["conmon → Container A<br/>(User Namespace)"]
    P_Crun --> P_Conmon2["conmon → Container B<br/>(User Namespace)"]

Die vier Kernpfeiler von Podman

  • Rootless by Design


    Container laufen unter regulären Benutzerkonten mittels Linux User Namespaces (subuid/subgid). Selbst root im Container wird auf eine unprivilegierte hohe UID auf dem Host abgebildet.

  • Daemonless Fork/Exec


    Kein persistenter Hintergrunddienst, der RAM belegt oder einen Single Point of Failure darstellt. Jeder Container wird von einem schlanken C-Prozess (conmon) überwacht und nativ vom Linux-Kernel verwaltet.

  • Native Kubernetes-Pods


    Erstklassige Unterstützung für Multi-Container-Pods, die sich Netzwerk- (localhost) und IPC-Namespaces teilen – inklusive nahtlosem Export und Import von Kubernetes-YAML-Manifesten.

  • Systemd- & Quadlet-Integration


    Container starten als native systemd-Dienste über deklarative Quadlet-Units (.container, .pod, .network), inklusive journald-Logging und automatischen Image-Rollbacks.

Technischer Vergleich: Podman vs. Docker

Merkmal / Dimension Podman (v5.x) Docker Engine
Ausführungsmodell Daemonless (fork/exec + conmon) Client-Server (dockerd + containerd)
Standard-Rechtemodell Rootless (Nativer Standard) Rootful Daemon als Standard (Rootless optional)
Standard OCI-Runtime crun (Schnelle, ressourcenschonende C-Engine) runc (Go-Implementierung)
Systemd-Integration Native deklarative Quadlets (systemd-Generator) Separat über Restart-Policies von dockerd
Multi-Container-Einheit Native Pods + podman kube play + Compose Docker Compose / Swarm
CLI-Kompatibilität 100 % Drop-in-kompatibel (podman run, build, ps) Standard Docker-CLI

2. Installation & Konfigurationshierarchie

Podman ist in den offiziellen Paketquellen aller großen Linux-Distributionen enthalten. Für vollwertige Rootless-Funktionalität müssen User-Namespace-Mapping (uidmap), Rootless-Overlay-Dateisysteme (fuse-overlayfs) und User-Space-Netzwerk-Tools (passt / slirp4netns) installiert sein.

Terminal: Podman & Rootless-Stack unter Debian/Ubuntu installieren
sudo apt update
sudo apt install -y podman uidmap fuse-overlayfs slirp4netns passt

# Version und Rootless-Laufzeitstatus überprüfen
podman --version
podman info --format '{{.Host.Security.Rootless}}'
Terminal: Podman unter Fedora/RHEL installieren
sudo dnf install -y podman passt

# Optional: Docker-Compose-Kompatibilitätsschicht
sudo dnf install -y podman-compose
Terminal: Podman unter Arch Linux installieren
sudo pacman -Syu --needed podman fuse-overlayfs slirp4netns passt
Terminal: Podman unter openSUSE installieren
sudo zypper refresh
sudo zypper install -y podman fuse-overlayfs slirp4netns passt

Pre-Flight-Check: Untergeordnete UID/GID-Zuordnung (subuid & subgid)

Rootless Podman erfordert einen Block untergeordneter UIDs (typischerweise 65.536 IDs), die deinem Benutzernamen in /etc/subuid und /etc/subgid zugewiesen sind:

Terminal: Subordinate UID/GID-Zuweisung prüfen
grep "^$(whoami):" /etc/subuid /etc/subgid

Erwartete Ausgabe:

/etc/subuid:nejat:100000:65536
/etc/subgid:nejat:100000:65536

Fehlende Einträge in subuid / subgid?

Falls der obige Befehl auf einem Minimal-Server leer bleibt, weise deinem Benutzer einen Bereich von 65.536 IDs zu und migriere das Mapping:

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $(whoami)
podman system migrate

Priorität der Konfigurationsdateien (TOML)

Podman liest Konfigurationen in einer klaren dreistufigen Hierarchie: Interne Standards → Systemweite Konfiguration (/etc/containers/) → Benutzer-Overrides (~/.config/containers/).

  • containers.conf


    Steuert das Laufzeitverhalten: Standard-OCI-Runtime (crun), Netzwerk-Backend (netavark), cgroup_manager = "systemd", Standard-Zeitzone (tz = "local") und globale Umgebungsvariablen.

  • storage.conf


    Definiert den Storage-Treiber (overlay) und Speicherpfade (graphroot): /var/lib/containers/storage für Root, ~/.local/share/containers/storage für unprivilegierte Benutzer.

  • registries.conf


    Setzt unqualified-search-registries (z. B. ["docker.io", "quay.io"]), konfiguriert Pull-Through-Caches oder blockiert nicht vertrauenswürdige Registries.

  • policy.json


    Erzwingt kryptografische Signaturprüfungen für Images (z. B. Anforderung von GPG- oder Sigstore-Cosign-Signaturen in Produktionsumgebungen).

Best Practice: Immer vollqualifizierte Image-Namen (FQIN) nutzen

Statt podman pull alpine einzugeben, gib immer die exakte Registry-Domain an: podman pull docker.io/library/alpine:latest. Das verhindert Abfragen und schützt vor Registry-Squatting-Angriffen.

Hands-On Lab 1: Podman-Umgebung inspizieren & Benutzer-Konfiguration anlegen

Führe diese Befehle als regulärer Benutzer aus (ohne sudo!), um deine Engine zu überprüfen und die automatische Zeitzonen-Synchronisation für alle Container zu aktivieren:

Terminal: Engine inspizieren & benutzerdefinierte containers.conf anlegen
# 1. Zentrale Pfade und Laufzeit-Backends prüfen
podman info | grep -E "graphRoot|ociRuntime|networkBackend|rootless:"

# 2. Benutzer-containers.conf anlegen, die die Host-Zeitzone an alle Container weiterreicht
mkdir -p ~/.config/containers
cat << 'EOF' > ~/.config/containers/containers.conf
[containers]
tz = "local"
EOF

# 3. Mit einem flüchtigen Alpine-Container überprüfen
podman run --rm docker.io/library/alpine:latest date

3. Arbeiten mit OCI-Images & Multi-Stage-Builds

Container-Images sind unveränderliche Vorlagen aus schreibgeschützten OverlayFS-Schichten (Layers). Beim Starten eines Containers platziert Podman eine dünne, flüchtige Read-Write-Schicht (upperdir) über den schreibgeschützten Schichten (lowerdir).

Wichtige Befehle zur Image-Verwaltung

Operation Beispielbefehl Zweck
Suchen podman search --filter=is-official nginx Offizielle Repositories in Registries suchen
Pull via Tag podman pull docker.io/library/redis:7-alpine Bestimmtes Versions-Tag herunterladen
Pull via Digest podman pull alpine@sha256:beef... Unveränderlichen kryptografischen Hash herunterladen
Images auflisten podman images --digests Lokale Images, Größen und SHA256-Digests anzeigen
Layer inspizieren podman image tree alpine:latest Schichtenhierarchie des Images visualisieren
Bereinigen podman image prune -a -f Speicherplatz ungenutzter Images freigeben

Produktionsreife Images erstellen (Containerfile)

Podman nutzt im Hintergrund Buildah, um Containerfile- (oder Dockerfile-)Definitionen zu bauen. Für maximale Produktionssicherheit gelten drei Regeln: 1. Minimale Basis-Images nutzen (alpine oder distroless). 2. Privilegien im Image mittels unprivilegierter USER-Instruktion ablegen. 3. RUN-Befehle verketten und Paket-Caches in derselben Schicht bereinigen.

Hands-On Lab 2: Gehärtetes Webserver-Image bauen und ausführen

Wir bauen ein minimalistisches Nginx-Image aus einem Containerfile und inspizieren dessen Layer-Struktur:

Terminal: OCI-Image erstellen, bauen und analysieren
# 1. Isoliertes Testverzeichnis erstellen
mkdir -p /tmp/podman-build-lab && cd /tmp/podman-build-lab

# 2. Eigene Startseite erstellen
cat << 'EOF' > index.html
<!DOCTYPE html>
<html>
<body style="font-family: sans-serif; background: #0f172a; color: #38bdf8; text-align: center; padding-top: 10%;">
    <h1>🦭 Bereitgestellt von Rootless Podman!</h1>
    <p>Gebaut aus einem sauberen Containerfile mit Buildah.</p>
</body>
</html>
EOF

# 3. Containerfile definieren
cat << 'EOF' > Containerfile
FROM docker.io/library/nginx:stable-alpine
LABEL maintainer="Nejat Hakan <nejat.hakan@googlemail.com>"
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
EOF

# 4. Image bauen und Schichtenbaum analysieren
podman build -t localhost/nh-web:v1 .
podman image tree localhost/nh-web:v1

# 5. Testverzeichnis bereinigen
cd ~ && rm -rf /tmp/podman-build-lab

4. Container-Lifecycle steuern & Ressourcen begrenzen

Der Befehl podman run erzeugt und startet Container mit präziser Kontrolle über Ressourcen-Limits (cgroups v2), Umgebungsvariablen und Sicherheitsrichtlinien.

Anatomie eines produktionsreifen 'podman run'-Befehls
podman run -d \
  --name prod-api \
  --memory="512m" \
  --cpus="1.5" \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  -p 127.0.0.1:8080:80 \
  localhost/nh-web:v1
  • -p 127.0.0.1:8080:80: Bindet den Port strikt an das Loopback-Interface des Hosts (sodass nur ein lokaler Reverse-Proxy wie Nginx darauf zugreifen kann, anstatt den Port öffentlich ins Internet zu exponieren).
  • --memory="512m" --cpus="1.5": Erzwingt harte Grenzwerte über cgroups v2, damit ein fehlerhafter Container den Host nicht lahmlegen kann.
  • --read-only: Hängt das Root-Dateisystem des Containers schreibgeschützt ein. Das verhindert, dass Angreifer bei einer Kompromittierung Binärdateien manipulieren können.

Spickzettel für Lifecycle & Debugging

Terminal: Alltägliche Container-Operationen
# Laufende Container auflisten (-a für alle, --size für Speicherverbrauch)
podman ps -a --size

# Echtzeit-Logs der letzten 10 Minuten anzeigen
podman logs -f --since 10m prod-api

# Live-Ressourcenverbrauch anzeigen (CPU, RAM, Netzwerk-I/O via cgroups v2)
podman stats --no-stream

# Prozesse innerhalb des Containers aus Host-Sicht anzeigen
podman top prod-api user,pid,hpid,comm

# Interaktive Shell im laufenden Container für Debugging öffnen
podman exec -it prod-api /bin/sh

5. Podman-Netzwerk: Netavark, Pasta & Micro-Segmentierung

Mit Podman 4.0 und 5.0 wurde das Netzwerk-Subsystem grundlegend modernisiert:

  1. Netavark & Aardvark-DNS: Ein performanter, in Rust geschriebener Netzwerk-Stack zur Konfiguration von Bridge-Schnittstellen, Firewall-Regeln (nftables/iptables) und automatischer Container-DNS-Auflösung (aardvark-dns).
  2. Pasta (passt): Das moderne Rootless-Netzwerk-Backend (Ablösung des älteren slirp4netns), das einen schnellen Layer-2/Layer-4-Tunnel ohne Root-Rechte bereitstellt.

Multi-Tier-Netzwerk-Isolation

In Produktionsumgebungen sollte ein öffentlich erreichbarer Frontend-Container niemals im selben Netzwerk wie alle übrigen Dienste liegen. Stattdessen werden isolierte Netzwerke aufgebaut:

%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
    Internet(("🌐 Host :8080")) ==> Frontend["🟢 Frontend-Container<br/>(Nginx)"]

    subgraph FrontNet ["🔒 Netzwerk: frontend-net (10.89.1.0/24)"]
        Frontend <==>|"DNS: http://backend:5000"| Backend["🟡 Backend-API<br/>(Dual-Homed)"]
    end

    subgraph BackNet ["🔐 Netzwerk: backend-net (10.89.2.0/24 --internal)"]
        Backend <==>|"DNS: postgres://db:5432"| DB[("🔴 PostgreSQL DB<br/>(Kein Internetzugriff)")]
    end
  • frontend ist nur mit frontend-net verbunden und kann db weder pingen noch ansprechen.
  • backend ist sowohl mit frontend-net als auch mit backend-net verbunden und fungiert als einziges Gateway.
  • backend-net wird mit --internal erstellt, wodurch dem Datenbank-Container jeglicher ausgehende Internetzugriff verwehrt wird.

Hands-On Lab 3: Zero-Trust-Netzwerksegmentierung erzwingen

Erstelle die im Diagramm dargestellte 3-Tier-Topologie und prüfe den strikten Verbindungsabbruch:

Terminal: Isolierte Netzwerke aufbauen und testen
# 1. Zwei isolierte Netzwerke anlegen (backend-net mit --internal = kein Internet)
podman network create frontend-net --subnet 10.89.1.0/24
podman network create backend-net --subnet 10.89.2.0/24 --internal

# 2. Datenbank (nur in backend-net) und Frontend (nur in frontend-net) starten
podman run -d --name db --network backend-net docker.io/library/alpine:latest sleep infinity
podman run -d --name frontend --network frontend-net docker.io/library/alpine:latest sleep infinity

# 3. Backend starten und mit BEIDEN Netzwerken verbinden
podman run -d --name backend --network backend-net docker.io/library/alpine:latest sleep infinity
podman network connect frontend-net backend

# 4. Verifikation: Backend erreicht die Datenbank via DNS (ERFOLG)
podman exec backend ping -c 2 db

# 5. Verifikation: Frontend KANN die Datenbank NICHT erreichen (SCHLÄGT FEHL)
podman exec frontend ping -c 2 db || echo ">>> ERFOLG: Frontend ist strikt von DB isoliert! <<<"

# 6. Container und Netzwerke bereinigen
podman rm -f frontend backend db
podman network rm frontend-net backend-net

6. Persistenter Speicher: Named Volumes vs. Bind Mounts

Da die beschreibbare Schicht eines Containers beim Löschen (podman rm) zerstört wird, müssen zustandsbehaftete Daten (Datenbanken, Uploads, Zertifikate) außerhalb des Containers gespeichert werden:

Werden vollständig von Podman unter ~/.local/share/containers/storage/volumes/ verwaltet. Podman passt UID/GID-Berechtigungen für Rootless-Container automatisch an.

Terminal: Lifecycle eines Named Volumes
# 1. Volume erstellen und Pfad auf dem Host inspizieren
podman volume create pg-data
podman volume inspect pg-data --format '{{.Mountpoint}}'

# 2. Volume in Container einhängen
podman run -d --name postgres-db \
  -v pg-data:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=SicheresPasswort \
  docker.io/library/postgres:16-alpine

# 3. Test bereinigen
podman rm -f postgres-db && podman volume rm pg-data

Hängt ein beliebiges Verzeichnis des Host-Dateisystems in den Container ein. Für reine Konfigurationen sollte immer :ro (schreibgeschützt) und auf SELinux-Systemen :Z (privat) genutzt werden.

Terminal: Schreibgeschützter Bind Mount für Konfigurationen
podman run -d --name web \
  -v /home/nejat/my-nginx.conf:/etc/nginx/nginx.conf:ro,Z \
  docker.io/library/nginx:stable-alpine

Liegt direkt im RAM des Hosts – berührt niemals die physische SSD und verschwindet rückstandslos beim Stoppen des Containers.

Terminal: Flüchtigen RAM-Speicher einbinden
podman run --rm -it --tmpfs /run/secrets:rw,noexec,nosuid,size=16m docker.io/library/alpine:latest df -h /run/secrets

Berechtigungsprobleme bei Rootless Bind Mounts lösen (:U)

Wenn ein Container intern unter einer Nicht-Root-UID läuft (z. B. UID 999 bei Postgres), weist ein Verzeichnis auf dem Host (Besitzer: UID 1000) Schreibzugriffe oft ab.

Lösung: Hänge das Flag :U an den Mount an (-v ./data:/var/lib/data:Z,U). Podman führt automatisch ein chown auf dem Host-Verzeichnis passend zum gemappten User-Namespace des Containers durch!


7. Multi-Container-Pods orchestrieren

Direkt aus Kubernetes übernommen, fasst ein Pod mehrere Container zusammen, die sich dieselben Netzwerk-, IPC- und UTS-Namespaces teilen.

Beim Erstellen eines Pods erzeugt Podman einen winzigen infra (pause) Container, dessen einzige Aufgabe darin besteht, den Netzwerk-Namespace offen zu halten. Jeder hinzugefügte Anwendungscontainer teilt sich dieselbe IP-Adresse und kommuniziert über blitzschnelles localhost-Loopback:

%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
    HostPort(("🌐 Host-Port 8080")) ==>|"Weiterleitung auf Pod-Ebene"| Infra

    subgraph Pod ["📦 Podman Pod: webapp-pod (Geteilter Netzwerk-Namespace / 10.88.0.x)"]
        direction TB
        Infra["⚙️ infra (pause) Container<br/>Hält Net- & IPC-Namespaces offen"]
        Nginx["🟢 Container 1: Nginx-Proxy<br/>Lauscht auf 0.0.0.0:80"]
        App["🐍 Container 2: Backend-API<br/>Lauscht auf 127.0.0.1:8000"]

        Nginx <==>|"Loopback: 127.0.0.1:8000"| App
    end

Hands-On Lab 4: Pod mit Reverse-Proxy starten & als Kubernetes-YAML exportieren

Port-Weiterleitungen (-p 8080:80) werden einmalig auf dem Pod definiert, während Webserver und Backend direkt über localhost kommunizieren:

Terminal: Pod erstellen, Container hinzufügen & Kubernetes-Manifest exportieren
# 1. Pod erstellen und Port 8080 auf Port 80 im Pod weiterleiten
podman pod create --name demo-pod -p 127.0.0.1:8080:80

# 2. Python-HTTP-Backend im Pod auf Port 8000 starten
podman run -d --pod demo-pod --name backend-svc \
  docker.io/library/python:3.12-alpine \
  python3 -m http.server 8000

# 3. Aus einem zweiten Container im SELBEN Pod verifizieren, dass localhost:8000 antwortet
podman run --rm --pod demo-pod docker.io/library/alpine:latest \
  wget -qO- http://127.0.0.1:8000 | head -n 5

# 4. Laufenden Pod als vollwertiges Kubernetes-YAML-Manifest exportieren
podman kube generate demo-pod > /tmp/demo-pod-k8s.yaml
head -n 25 /tmp/demo-pod-k8s.yaml

# 5. Gesamten Pod mit einem einzigen Befehl beenden und bereinigen
podman pod rm -f demo-pod && rm -f /tmp/demo-pod-k8s.yaml

8. Container als Systemd-Dienste betreiben (Quadlet)

In Produktivsystemen müssen Container beim Systemstart automatisch hochfahren, nach Abstürzen neu starten und zentral in journald protokollieren.

Veraltet: podman generate systemd → Quadlet

Seit Podman 4.4 und 5.0 ist der imperative Befehl podman generate systemd offiziell veraltet. Der moderne Standard ist Quadlet – deklarative Unit-Dateien (.container, .volume, .network, .pod), die von systemd dynamisch beim Laden in native Dienste übersetzt werden.

Deklarative .container-Dateien werden in ~/.config/containers/systemd/ (rootless) oder /etc/containers/systemd/ (rootful) abgelegt.

~/.config/containers/systemd/nh-webserver.container
[Unit]
Description=Rootless Nginx-Webserver via Podman Quadlet
After=network-online.target
Wants=network-online.target

[Container]
Image=docker.io/library/nginx:stable-alpine
ContainerName=nh-webserver
AutoUpdate=registry
PublishPort=127.0.0.1:8088:80
ReadOnly=true
Tmpfs=/var/cache/nginx /var/run

[Service]
Restart=always
TimeoutStartSec=300

[Install]
WantedBy=default.target

Quadlet aktivieren und verwalten:

Terminal: Systemd neu laden & Quadlet-Dienst starten
# 1. Lingering aktivieren, damit Dienste auch nach SSH-Logout weiterlaufen
sudo loginctl enable-linger $(whoami)

# 2. Systemd-Generator ausführen und Dienst starten
systemctl --user daemon-reload
systemctl --user start nh-webserver.service
systemctl --user status nh-webserver.service

# 3. Automatische Image-Aktualisierungen mit Rollback bei Fehlern durchführen
podman auto-update

Für ältere Systeme (Podman < 4.4) kann eine traditionelle Service-Datei erzeugt werden:

Terminal: Legacy Systemd-Unit generieren
mkdir -p ~/.config/systemd/user
podman generate systemd --new --name mein-container > ~/.config/systemd/user/container-mein-container.service
systemctl --user daemon-reload
systemctl --user enable --now container-mein-container.service

9. Defense-in-Depth: Rootless-Sicherheitsarchitektur

Die Ausführung als Rootless-Container bietet den stärksten Schutz gegen Container-Breakouts, da UID 0 im Container auf dem Host auf deine unprivilegierte Benutzer-UID abgebildet wird:

%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
    subgraph ContainerNS ["📦 Innerhalb des Container-User-Namespaces"]
        C_Root["Container root<br/>(UID 0)"]
        C_App["Container nginx<br/>(UID 101)"]
    end

    subgraph HostOS ["🖥️ Host-Linux-Kernel (Tatsächliche Rechte)"]
        H_User["🟢 Host-User 'nejat'<br/>(UID 1000 — Keine Root-Rechte)"]
        H_Sub["🟢 Host-SubUID 100100<br/>(Keine Rechte auf dem Host)"]
    end

    C_Root ==>|"Kernel UID-Mapping"| H_User
    C_App ==>|"Kernel SubUID-Mapping"| H_Sub

Die fünf Härtungsebenen von Podman

Sicherheitsebene Schalter / Mechanismus Schutzwirkung
1. User Namespaces Rootless-Standard (subuid) Bildet Container-root auf unprivilegierten Host-User ab
2. Linux Capabilities --cap-drop=ALL Entzieht alle Kernel-Privilegien (CAP_NET_RAW, CAP_SYS_ADMIN, etc.)
3. No New Privileges --security-opt=no-new-privileges Verhindert Rechteausweitung über setuid-Binaries
4. Seccomp-Filter Standard /usr/share/containers/seccomp.json Blockiert ~300+ gefährliche Kernel-Syscalls (kexec, reboot, mount)
5. Read-Only RootFS --read-only Verhindert Manipulation von Systemdateien bei Kompromittierung

Hands-On Lab 5: UID-Mapping verifizieren & Container maximal härten

Überprüfe selbst, dass root (UID 0) im Container auf dem Host lediglich deine normale Benutzer-ID ist:

Terminal: Host- vs. Container-UID prüfen & gehärteten Container starten
# 1. Zuordnung von UID 0 in /proc/self/uid_map prüfen
podman run --rm docker.io/library/alpine:latest cat /proc/self/uid_map

# 2. Container mit entzogenen Capabilities und schreibgeschütztem Dateisystem starten
podman run --rm \
  --cap-drop=ALL \
  --security-opt=no-new-privileges \
  --read-only \
  docker.io/library/alpine:latest \
  sh -c "id && grep CapEff /proc/self/status"
* CapEff: 0000000000000000 beweist: Trotz Ausgabe von uid=0(root) besitzt der Prozess keinerlei effektive Linux-Kernel-Capabilities!


10. Fehlerbehebung & Häufige Fallstricke

1. Warum kann mein Rootless-Container Port 80 oder 443 nicht binden?

Ursache: Standardmäßig verbietet der Linux-Kernel unprivilegierten Benutzern das Binden an Ports unterhalb von 1024 (net.ipv4.ip_unprivileged_port_start = 1024).

Produktions-Best-Practice: Binde den Rootless-Container an einen hohen Loopback-Port (-p 127.0.0.1:8080:80) und überlasse TLS und die öffentlichen Ports 80/443 einem vorgeschalteten Host-Reverse-Proxy (Nginx oder Caddy).

2. Warum stoppen Rootless-Container nach dem SSH-Logout?

Ursache: Systemd beendet Benutzersitzungen und Kindprozesse, sobald die letzte SSH-Verbindung schließt.

Lösung: Aktiviere das Lingering für dein Benutzerkonto:

sudo loginctl enable-linger $(whoami)

3. Permission Denied bei Bind-Mounts lokaler Host-Verzeichnisse

Ursache: Entweder blockiert SELinux/AppArmor den Zugriff, oder der Dienst im Container läuft als Nicht-Root-Sub-UID (z. B. UID 999), die keine Schreibrechte auf deinem Host-Verzeichnis hat.

Lösung: Hänge :Z,U an das Volume-Flag an (-v /host/pfad:/container/pfad:Z,U), damit Podman den passenden SELinux-Kontext setzt und die Dateirechte automatisch an die gemappte Container-UID anpasst.