Alle Rezepte
Container-Engines · Docker Engine 29 · Podman 6 · Architektur und Betrieb

Docker und Podman:
der Unterschied ist das Prozessmodell

Acht Abschnitte über zwei Werkzeuge, die dieselben Container starten und sich trotzdem anders verhalten: Daemon gegen Wächterprozess, Rechte, Netzwerk, Volumes, Image-Bau und Autostart. Beide bauen auf denselben Kernel-Funktionen auf – unterschiedlich ist, wer die Prozesse besitzt.

0Gemeinsame BasisOCI, Kernel, Begriffe 1ProzessmodellDaemon, conmon, Ausfall 2RechteRootless, Socket, Ausbruch 3NetzwerkDNS, Ports, Backends 4VolumesUID-Mapping, SELinux 5ImagesBuild, Registries, Namen 6BetriebAutostart, Compose, Pods 7UmstiegReihenfolge, Entscheidung
0

Gemeinsame Basis: worin sie sich nicht unterscheiden

Vergleiche starten oft beim Marketing. Nützlicher ist die Feststellung, wie viel identisch ist – der Rest des Artikels handelt nur noch von den Abweichungen.

Identisch bei beiden

  • Isolation – Namespaces, cgroups, seccomp, Capabilities und LSM wie SELinux oder AppArmor. Beide „erfinden" keine Isolation, sie konfigurieren den Kernel
  • Images – dasselbe OCI-Format, dieselben Registries, dieselben Layer
  • Dateiformat – Containerfile und Dockerfile sind dasselbe, nur anders benannt
  • Runtime – am Ende startet ein OCI-Runtime-Binary den Prozess: runc bei Docker, crun bei Podman (beide sind austauschbar)
  • Kommandos – run, ps, logs, exec, build, push heißen und wirken gleich
Beide sind Fernbedienungen für denselben Fernseher – der Kernel macht die Arbeit, die Werkzeuge drücken nur die Knöpfe.

Der eine Unterschied, aus dem alle anderen folgen

Docker ist eine Client-Server-Anwendung: Die CLI schickt Befehle an einen dauerhaft laufenden Dienst, der alle Container besitzt. Podman startet Container als gewöhnliche Kindprozesse des aufrufenden Benutzers – es gibt keinen zentralen Dienst, der etwas besitzt.

Daraus folgen die Themen der nächsten Abschnitte: Wem gehören die Prozesse (1), mit welchen Rechten laufen sie (2), wer richtet das Netzwerk ein (3), wem gehören die Dateien im Volume (4) und wer startet alles nach einem Reboot (6).

FrageDockerPodman
Wer besitzt die Container?dockerddein Benutzer
Rechte im Normalfallroot (rootless möglich)dein Benutzer (rootful möglich)
Zustand nach RebootDaemon startet Containersystemd startet Container
APIimmer da (Socket)optional (podman system service)
GruppierungCompose-ProjektePods und Compose
Hinweis: Beide Projekte haben sich aufeinander zubewegt. Docker kann rootless, Podman kann auf Wunsch als root und mit Docker-kompatibler API laufen. „Docker ist unsicher, Podman ist sicher" ist deshalb keine brauchbare Zusammenfassung – die genaue Betriebsart entscheidet, nicht der Name des Binaries.
1

Prozessmodell: Daemon gegen Wächterprozess

Der sichtbarste Unterschied steht in der Prozessliste. Er erklärt, warum ein Docker-Neustart alle Container betrifft und ein Podman-Problem meist nur einen.

Wer hängt an wem

Bei Docker geht jeder Befehl an dockerd; darunter verwaltet containerd je Container einen Shim-Prozess. Bei Podman baut der aufrufende Prozess den Container auf, übergibt ihn an conmon und beendet sich – zurück bleibt ein Wächter je Container, dessen Elternprozess systemd ist.

Docker Podman docker run … dockerd ein Prozess für alle Container · läuft als root containerd Lebenszyklus, Images shim runc Container shim runc Container shim runc Container dockerd neu gestartet: ohne live-restore sterben alle Container mit live-restore laufen sie weiter, docker ps antwortet trotzdem nicht podman run … kein Daemon der podman-Prozess endet nach dem Start stattdessen: ein Wächterprozess je Container conmon crun Container conmon crun Container conmon crun Container Elternprozess ist systemd bzw. init – kein gemeinsamer Vorfahr Ein hängender Container betrifft nur seinen conmon; es gibt keinen Prozess, dessen Ausfall alle betrifft

Praktisch heißt das: ps -ef zeigt bei Podman keine gemeinsame Wurzel. Genau deshalb kann Podman Container nicht „von sich aus" nach einem Reboot starten – dazu später mehr in Abschnitt 6.

Was ein Ausfall bedeutet

  • Docker – stirbt der Daemon, sind alle Verwaltungsbefehle tot. Ob die Container weiterlaufen, hängt an live-restore
  • Podman – stirbt ein conmon, betrifft das genau einen Container. Es gibt keinen Prozess, dessen Ausfall alle betrifft

Was ein Update bedeutet

  • Docker – ein Engine-Update ist ein Neustart des Daemons und damit ein Eingriff in alle Container gleichzeitig
  • Podman – ein Update tauscht ein Binary aus; laufende Container merken davon nichts, weil sie nicht an ihm hängen
Achtung: Die verbreitete Grafik „Daemon tot, Container laufen weiter" stimmt nur mit "live-restore": true in der daemon.json – und diese Option ist nicht der Standard und mit Swarm unvereinbar. Ohne sie beendet ein Daemon-Neustart alle Container. Umgekehrt gilt mit live-restore: Die Container laufen zwar, aber docker ps, Logs und Health-Status sind währenddessen nicht verfügbar. Beides sollte man wissen, bevor man den Punkt in einer Architekturdiskussion anführt.
Prozessbaum selbst ansehen

Der Unterschied lässt sich in zwei Befehlen belegen – nützlich, wenn in einer Diskussion behauptet wird, Podman habe „auch einen Daemon".

# Docker: alles hängt unter dockerd bzw. containerd
ps -ef --forest | grep -A 5 dockerd

# Podman: je Container ein conmon, Elternprozess systemd
ps -ef --forest | grep conmon
systemctl --user status

Sichtbar wird außerdem, dass podman run im Vordergrund kein Client ist, der wartet, sondern selbst der Prozess, der den Container aufbaut.

Docker ist ein Restaurant mit Zentralküche, Podman eine Etagenküche – fällt der Chefkoch aus, steht das Restaurant; kocht jeder auf seiner Etage, betrifft ein Küchenbrand nur eine Wohnung.
2

Rechte: was root im Container auf dem Host bedeutet

In beiden Fällen ist der Prozess im Container standardmäßig root. Der Unterschied ist, worauf diese Null auf dem Host abgebildet wird.

Der Eskalationspfad im Vergleich

Ein Ausbruch aus dem Container ist kein theoretischer Fall – er ist der Grund, warum Rootless existiert. Entscheidend ist, welche Rechte der ausgebrochene Prozess auf dem Host hat.

Docker · rootful der Normalfall Prozess im Container root (uid 0) Ausbruch aus dem Container auf dem Host uid 0 – echter root Vollzugriff auf den Host Docker · rootless optionales Setup Prozess im Container root (uid 0) Ausbruch aus dem Container auf dem Host deine subuid, z. B. 100000 nur die Rechte deines Users Podman · rootless Standard Prozess im Container root (uid 0) Ausbruch aus dem Container auf dem Host deine subuid, z. B. 100000 nur die Rechte deines Users In allen drei Fällen ist der Prozess im Container root – der Unterschied liegt darin, was diese Null auf dem Host bedeutet.

Rootless heißt nicht, dass im Container kein root existiert, sondern dass dieses root über User-Namespaces auf einen unprivilegierten Bereich abgebildet wird – festgelegt in /etc/subuid und /etc/subgid.

Was rootless kostet

EinschränkungGrundAusweg
Ports unter 1024privilegierte Portsnet.ipv4.ip_unprivileged_port_start senken oder auf höheren Port veröffentlichen
Datei-Eigentümer im VolumeUID-Abbildung:U, --userns=keep-id
Manche Netzwerkmodikeine Rechte für Bridges am Hostrootful betreiben oder Portweiterleitung nutzen
Overlay-Dateisystemkernelabhängignative Overlay-Unterstützung oder fuse-overlayfs
Achtung: Der Zugriff auf /var/run/docker.sock ist gleichbedeutend mit root auf dem Host – auch als „nur lesend" eingebundenes Volume, denn über die API lässt sich ein privilegierter Container mit gemountetem Wurzelverzeichnis starten. In CI-Runnern, Monitoring-Agenten und Deploy-Tools wird dieser Mount routinemäßig als harmlose Bequemlichkeit behandelt. Wer Podman rootless nutzt, hat dieses Problem nicht, weil es keinen privilegierten Socket gibt.
Der Docker-Socket ist der Generalschlüssel des Hauses – wer ihn ausleiht, verleiht nicht ein Zimmer, sondern alle.
Rootless prüfen und einrichten

Vor der Fehlersuche lohnt die Feststellung, in welchem Modus man überhaupt arbeitet – der Unterschied ist an der Ausgabe sofort erkennbar.

podman info --format '{{.Host.Security.Rootless}}'
id -u
grep "^$USER:" /etc/subuid /etc/subgid

Fehlen die subuid-Bereiche, startet rootless gar nicht erst sauber. Nachtragen und danach die Zuordnung neu aufbauen lassen.

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 <user>
podman system migrate

Bei Docker ist rootless kein Schalter, sondern eine eigene Installation je Benutzer.

dockerd-rootless-setuptool.sh install
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
3

Netzwerk: zwei Stacks mit denselben Begriffen

Die Kommandos sehen gleich aus, die Umsetzung darunter ist verschieden. Wichtig wird das bei DNS zwischen Containern, bei veröffentlichten Ports und bei Firewall-Regeln.

Wer macht was

AufgabeDockerPodman
Bridge und Regelneingebaut, nftables oder iptablesnetavark, seit Podman 6 nftables
DNS zwischen Containerneingebauter Resolveraardvark-dns
Rootless-Netzgvisor-tap-vsock (Docker 29)pasta (Podman 5/6)
Standardnetzbridgepodman

Gemeinsam ist beiden eine Falle, die nichts mit dem Werkzeug zu tun hat: Im vorgegebenen Standardnetz gibt es keine Namensauflösung zwischen Containern. Die gibt es erst in einem selbst angelegten Netz.

podman network create appnet
podman run -d --network appnet --name db postgres:16
podman run --rm --network appnet alpine ping -c1 db
Achtung: Im rootless-Modus sieht der Server die Quelladresse nicht so, wie man es erwartet. Verbindungen von außen erscheinen je nach Portweiterleitung mit der Adresse des Weiterleiters statt mit der echten Client-IP. Zugriffslisten, Fail2ban-Regeln und Logauswertungen, die auf Client-Adressen aufbauen, funktionieren dann scheinbar – sie sehen nur überall dieselbe Adresse. Bei Podman hilft --network=pasta mit passenden Optionen, sonst bleibt rootful.
Portveröffentlichung und Firewall prüfen

Wenn ein veröffentlichter Port nicht antwortet, ist die erste Frage, ob überhaupt jemand lauscht – und auf welcher Adresse.

podman port <container>
ss -tlnp | grep <port>

Bei Docker kommt die Besonderheit dazu, dass veröffentlichte Ports Firewall-Regeln erzeugen, die an einer eventuell konfigurierten Host-Firewall vorbeigehen können. Die tatsächlich aktiven Regeln stehen im Firewall-Backend, nicht in der Werkzeugausgabe.

# je nach Backend
sudo nft list ruleset | grep -i docker
sudo iptables -t nat -L DOCKER -n

Docker 29 kann nftables als Backend nutzen; Podman 6 hat den iptables-Weg entfernt. Auf gemischten Hosts lohnt der Blick, welches Backend gerade aktiv ist, bevor Regeln von Hand ergänzt werden.

Das Standardnetz ist ein Großraumbüro ohne Namensschilder – alle sitzen im selben Raum, aber niemanden kann man beim Namen rufen. Ein eigenes Netz verteilt die Schilder.
4

Volumes: wem die Dateien gehören

Der häufigste Stolperstein beim Umstieg. Ein Bind-Mount, der unter Docker funktioniert, liefert unter Podman rootless „Permission denied" – ohne dass an den Rechten etwas falsch wäre.

Warum die Rechte anders wirken

Rootless bildet die UIDs im Container auf einen subuid-Bereich ab. Wer im Container als root schreibt, erzeugt auf dem Host Dateien, die einer UID wie 100000 gehören – nicht dir. Umgekehrt gehören deine Dateien im Container einer UID, die dort niemand kennt.

SituationMittelWirkung
Container soll in dein Verzeichnis schreiben--userns=keep-iddeine UID bleibt im Container dieselbe
Bestehende Daten passend machen:U am MountPodman ändert den Eigentümer rekursiv
SELinux-System (RHEL, Fedora):Z oder :zsetzt den Kontext, sonst Zugriff verweigert
# eigener User im Container, SELinux-Kontext privat setzen
podman run --rm -v ./data:/data:Z,U --userns=keep-id alpine touch /data/probe
Achtung: Bei „Permission denied" im rootless-Betrieb greift der Reflex zu chmod 777. Das behebt nichts, weil die Ursache nicht die Zugriffsmaske ist, sondern die UID-Abbildung oder der SELinux-Kontext – dafür bleibt danach ein weltweit beschreibbares Verzeichnis zurück. Erst ls -n auf dem Host und id im Container vergleichen, dann :U, keep-id oder :Z einsetzen.
Zwei Straßenverzeichnisse für dieselbe Straße – die Hausnummer 0 im Container ist auf dem Host die 100000. Der Brief kommt an, nur eben bei jemand anderem.
Wo die Daten liegen

Rootless speichert alles im Benutzerprofil, nicht unter /var/lib. Das ist der Grund, warum Images nach einem Wechsel zwischen sudo podman und podman scheinbar verschwinden – es sind zwei getrennte Bestände.

podman info --format '{{.Store.GraphRoot}}'
# rootless: ~/.local/share/containers/storage
# rootful:  /var/lib/containers/storage
docker info --format '{{.DockerRootDir}}'

Praktische Folge: Ein Image, das mit sudo gezogen wurde, steht dem unprivilegierten Aufruf nicht zur Verfügung, und umgekehrt. Bei Platzproblemen lohnt der Blick in beide Verzeichnisse.

5

Images: bauen, benennen, beziehen

Gleiches Format, andere Werkzeuge darunter – und eine Namensauflösung, die sich anders verhält, als man es von Docker gewohnt ist.

Docker

  • BuildKit – paralleler Bau, Cache-Mounts, Secrets im Build
  • Image-Store – ab Docker 29 bei Neuinstallationen containerd statt overlay2
  • Namen – unqualifizierte Namen gehen immer zu Docker Hub

Podman

  • Buildah – podman build ist Buildah unter anderem Namen; Buildah kann zusätzlich ohne Containerfile bauen
  • Skopeo – Images zwischen Registries kopieren und prüfen, ohne sie zu ziehen
  • Namen – registries.conf entscheidet, wohin ein unqualifizierter Name zeigt
Achtung: podman run nginx ist nicht dasselbe wie docker run nginx. Podman löst kurze Namen über die Suchliste in /etc/containers/registries.conf auf – je nach Distribution und Konfiguration wird interaktiv nachgefragt, eine andere Registry bevorzugt oder der Aufruf schlägt fehl. In Skripten, Dockerfiles und CI-Pipelines gehören Namen deshalb vollständig qualifiziert: docker.io/library/nginx:1.27. Sonst zieht dieselbe Zeile auf zwei Hosts zwei verschiedene Images.
Build-Unterschiede, die im Alltag auffallen

Beide bauen aus derselben Datei, aber die BuildKit-Erweiterungen sind Docker-spezifisch. Ein Containerfile mit Cache-Mounts läuft unter Podman nicht zwingend gleich.

# BuildKit-Syntax – unter Podman nicht in jedem Fall unterstützt
RUN --mount=type=cache,target=/root/.cache/go-build go build ./...

Wer beide Wege bedienen muss, hält den Bau bei den Grundfunktionen und legt Abhängigkeiten in eigene Schichten, statt sich auf Cache-Mounts zu verlassen.

Nützlich und ohne Docker-Gegenstück ist das Prüfen und Kopieren von Images ohne lokalen Pull:

skopeo inspect docker://docker.io/library/nginx:1.27
skopeo copy docker://docker.io/library/nginx:1.27 docker://registry.intern/nginx:1.27
Hinweis: Für getrennte Umgebungen ist das der praktische Vorteil des Podman-Umfelds: Skopeo spiegelt Images ohne laufende Engine, und die Signaturprüfung wird zentral in policy.json festgelegt statt pro Aufruf.
6

Betrieb: Autostart, Compose, Pods

Hier wird das Prozessmodell zur Alltagsfrage: Ohne Daemon gibt es niemanden, der nach einem Reboot etwas startet. Diese Rolle übernimmt systemd.

Wer startet den Container nach dem Reboot

DockerPodman
Zuständigder Daemonsystemd
Konfiguration--restart=alwaysQuadlet-Unit oder podman-restart.service
Ablageim Daemon-ZustandDatei unter ~/.config/containers/systemd/
Für Benutzerdienste–loginctl enable-linger <user>

Quadlet ist der empfohlene Weg: Eine kurze Unit-Datei beschreibt den Container, systemd erzeugt daraus beim Neuladen einen Dienst. Das frühere Erzeugen von Unit-Dateien aus laufenden Containern gilt als überholt.

# ~/.config/containers/systemd/web.container
[Container]
Image=docker.io/library/nginx:1.27
PublishPort=8080:80

[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user start web
loginctl enable-linger $USER
Achtung: podman run --restart=always wird ohne Fehlermeldung angenommen und wirkt im laufenden Betrieb – nach einem Reboot ist der Container trotzdem weg, weil kein Daemon existiert, der ihn wieder startet. Es braucht eine Quadlet-Unit oder den aktivierten podman-restart.service, und für Benutzerdienste zusätzlich enable-linger, sonst beendet systemd beim Abmelden alles. Das ist der häufigste Umstiegsfehler überhaupt: In der Testphase läuft alles, der erste Neustart des Servers räumt den Dienst ab.

Compose

Compose-Dateien laufen zum großen Teil unverändert. Zwei Wege: podman-compose als eigenständige Umsetzung oder das originale Compose gegen den Podman-Socket.

systemctl --user enable --now podman.socket
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
docker compose up -d

Pods und Kubernetes

Podman kennt Pods als echtes Objekt – mehrere Container teilen sich einen Netzwerk-Namespace, genau wie in Kubernetes. Daraus lässt sich YAML erzeugen und wieder abspielen.

podman pod create --name app -p 8080:80
podman kube generate app > app.yaml
podman kube play app.yaml
Hinweis: Das erzeugte YAML ist ein guter Start, aber kein fertiges Deployment-Manifest: Es beschreibt Pods, keine Deployments, Services oder Ressourcengrenzen. Als Brücke zwischen lokalem Ausprobieren und einem Cluster taugt es, als Produktionsartefakt nicht.
Tools, die einen Docker-Socket erwarten

alias docker=podman deckt die Kommandozeile ab, aber nicht Werkzeuge, die direkt mit der API sprechen – etwa Testcontainers, Buildpacks oder manche CI-Runner. Für sie braucht es den kompatiblen Socket.

systemctl --user enable --now podman.socket
podman info --format '{{.Host.RemoteSocket.Path}}'

Danach erwarten die meisten Werkzeuge nur noch die passende Umgebungsvariable. Testcontainers braucht zusätzlich den Hinweis, dass der Socket nicht privilegiert ist.

export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
export TESTCONTAINERS_RYUK_DISABLED=true
7

Umstieg und Entscheidung, der Reihe nach

Ein Wechsel scheitert selten an den Kommandos und fast immer an Autostart, Volumes und Registry-Namen. In dieser Reihenfolge lässt sich das abarbeiten.

Die vier Phasen auf einen Blick

  1. Phase A · Betriebsart festlegenRootless oder rootful, und ob eine Docker-kompatible API gebraucht wird.
  2. Phase B · Images und NamenKurze Namen qualifizieren, Registries konfigurieren.
  3. Phase C · Daten und RechteVolumes, UID-Abbildung, SELinux-Kontexte klären.
  4. Phase D · Start und ÜberwachungQuadlet-Units schreiben, Reboot testen, Monitoring anpassen.

Phase A · Betriebsart

Schritt 1 · Rootless oder rootful entscheiden

Rootless ist der Normalfall und der eigentliche Gewinn. Rootful bleibt sinnvoll, wenn privilegierte Ports ohne Umweg, echte Client-Adressen oder besondere Netzwerkmodi gebraucht werden. Die Entscheidung fällt zuerst, weil sie Speicherort, Netzwerk und Rechte bestimmt.

podman info --format '{{.Host.Security.Rootless}}'

Phase B · Images

Schritt 2 · Namen qualifizieren

Jeder unqualifizierte Name in Compose-Dateien, Containerfiles und Skripten wird zur Fehlerquelle, sobald die Suchliste anders aussieht. Einmal durchsuchen und vollständige Namen eintragen.

grep -rn "image:" --include="*.yml" --include="*.yaml" . | grep -v "/"
podman info --format '{{.Registries}}'

Phase C · Daten

Schritt 3 · Volumes und Rechte klären

Vor der Umstellung feststellen, welcher Benutzer im Container schreibt und wem die Dateien auf dem Host gehören müssen. Danach entscheidet sich, ob keep-id, :U oder ein angepasster Benutzer im Image der richtige Weg ist.

podman run --rm -v ./data:/data:Z alpine sh -c 'id; ls -ln /data'

Bestehende Docker-Volumes liegen unter /var/lib/docker/volumes; sie werden nicht automatisch übernommen. Der einfachste Weg ist ein Archiv statt eines Kopierens auf Dateiebene, damit Rechte und Symlinks erhalten bleiben.

Phase D · Start und Überwachung

Schritt 4 · Quadlet schreiben und den Reboot testen

Der Test ist nicht „der Container läuft", sondern „der Container läuft nach einem Neustart des Hosts wieder". Ohne diesen Test bleibt der häufigste Umstiegsfehler unentdeckt.

systemctl --user daemon-reload
systemctl --user enable --now web
sudo reboot
# nach dem Neustart
systemctl --user status web
podman ps
Schritt 5 · Überwachung anpassen

Monitoring, das den Docker-Socket abfragt, sieht unter Podman nichts. Entweder den kompatiblen Socket bereitstellen oder auf systemd-Zustände umstellen – letzteres ist im rootless-Betrieb der geradere Weg.

systemctl --user list-units 'podman*' --no-pager
podman healthcheck run <container>

Wann welches Werkzeug

LageEmpfehlung
Einzelne Dienste auf einem ServerPodman rootless mit Quadlet
RHEL, Fedora, OpenShift-UmfeldPodman – gleiche Bibliotheken wie CRI-O, im System enthalten
Team arbeitet mit Compose und Desktop-WerkzeugenDocker, oder Podman mit aktiviertem Socket
Swarm im EinsatzDocker – Podman hat keine Entsprechung
CI-Runner ohne privilegierten SocketPodman rootless oder Buildah
Lernen für KubernetesPodman – Pods und YAML-Erzeugung entsprechen den Cluster-Begriffen
Die Wahl ist keine Glaubensfrage, sondern eine Betriebsfrage – wer startet nach dem Reboot, wem gehören die Dateien, wer darf den Socket. Wer diese drei Fragen beantworten kann, hat die Entscheidung schon getroffen.
Achtung: Beide Werkzeuge haben zuletzt Vorgaben geändert, die bei Upgrades still zubeißen. Podman 6 hat cgroups v1, die CNI-Netzwerke, slirp4netns und den iptables-Weg entfernt – auf älteren Hosts startet danach nichts mehr. Docker 29 stellt bei Neuinstallationen auf den containerd-Image-Store um, wodurch die zuvor vorhandenen lokalen Images nicht mehr auftauchen, obwohl sie noch auf der Platte liegen. Vor einem Major-Upgrade gehören beide Punkte geprüft, nicht danach.