Containerisierung & Systemhärtung mit Podman
Erstellt: 05.11.2026 · Zuletzt aktualisiert: 06.11.2026 · Nejat Hakan

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). Selbstrootim 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), inklusivejournald-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.
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:
Erwartete Ausgabe:
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:
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/storagefür Root,~/.local/share/containers/storagefü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:
# 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:
# 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.
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 übercgroups 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
# 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:
- 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). - Pasta (
passt): Das moderne Rootless-Netzwerk-Backend (Ablösung des älterenslirp4netns), 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
frontendist nur mitfrontend-netverbunden und kanndbweder pingen noch ansprechen.backendist sowohl mitfrontend-netals auch mitbackend-netverbunden und fungiert als einziges Gateway.backend-netwird mit--internalerstellt, 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:
# 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.
# 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.
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:
# 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.
[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:
# 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:
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:
# 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:
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.