SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-13

Podman vs Docker op een VPS: de werkelijke verschillen

Ontdek de impact van Podman op uw VPS. Zonder daemon en rootless verandert de omgang met systemd, poorten onder 1024, volume-rechten en Docker Compose bestanden in de praktijk.

Wat is het daadwerkelijke verschil tussen Podman en Docker

Podman en Docker draaien dezelfde OCI (Open Container Initiative) images op een VPS, dus de keuze hangt niet af van welke software u kunt draaien. Het verschil zit in het procesmodel. Docker draait een root-daemon die eigenaar is van elke container, en het docker-commando is een kleine client die aan die daemon vraagt om het werk uit te voeren. Podman heeft geen daemon: podman run start de container als een onderliggend proces van het proces dat het aanriep, onder uw eigen niet-geprivilegieerde gebruiker.

Al het overige vloeit voort uit dat ene feit. Automatisch opstarten wordt de taak van systemd in plaats van de daemon. Eigenaarschap van volumes verloopt via een user namespace, waardoor de eigenaar die u ziet met ls -l op de host niet de eigenaar is die de container ziet. Poorten onder 1024 weigeren te binden totdat u een kernelinstelling wijzigt. De docker CLI (command line interface) blijft werken via een wrapper, tot het punt waarop iets de Docker-socket vereist.

Geen daemon: wat er daadwerkelijk draait bij het starten van een container

Op een Docker-host toont pstree -a de dockerd als root, met containerd ernaast, en één containerd-shim-runc-v2 per actieve container. Uw applicatie is een kindproces van die shim, en de shim is een kindproces van PID 1. Niets verbindt de container met de shell die hem heeft opgestart. Stop de daemon en u verliest het controlepaneel voor elke container op de machine, en met de standaardinstelling live-restore uitgeschakeld, herstart systemctl restart docker ook uw containers.

Podman heeft geen equivalent proces. Start een container en u krijgt één conmon (container monitor)-proces dat het hoofdproces van de container vasthoudt, eigendom van de gebruiker die het commando uitvoerde.

podman run -d --name web -p 8080:80 docker.io/library/caddy:2
ps -o user,pid,ppid,args -C conmon
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080

ps hoort conmon weer te geven die draait als uw inloggebruiker en niet als root, en curl hoort 200 te printen. Omdat geen centrale service eigenaar is van de container, stopt sudo apt upgrade podman niets dat al draait, en het crashen van de monitor van één container kan de andere niet meesleuren.

De ontbrekende daemon kost u ook iets. Niets start uw containers na een reboot. De --restart=always van Docker is een belofte die de daemon bij het opstarten nakomt, en Podman vervangt dit door systemd, waarvoor de quadlet-sectie hieronder is bedoeld.

De socket is de andere helft van het verhaal. /var/run/docker.sock is een API (application programming interface)-eindpunt dat eigendom is van root, en elk proces dat hiernaar kan schrijven, kan een geprivilegieerde container starten die het host-bestandssysteem mount. Het toevoegen van een gebruiker aan de docker-groep verleent die gebruiker root-rechten via een omweg, wat het lezen waard is naast elk service-account alleen de toegang geven die het nodig heeft. Podman stelt geen socket bloot tenzij u daarom vraagt, en de socket die u krijgt, behoort toe aan één enkele gebruiker op /run/user/<uid>/podman/podman.sock.

Podman installeren op Ubuntu 24.04 en verifiëren of rootless actief is

sudo apt update
sudo apt install -y podman uidmap
podman --version
podman info | grep -i rootless

Het pakket uidmap levert newuidmap en newgidmap. Dit zijn de setuid-helpers waarmee een gewone gebruiker een bereik aan ondergeschikte ID's kan claimen; zonder deze helpers starten rootless containers niet. podman info hoort rootless: true weer te geven.

Ubuntu 24.04 levert Podman 4.9 en Debian 13 levert Podman 5.x, gecontroleerd in augustus 2026. Dit verschil is relevant, omdat quadlet-bestanden versie 4.4 of nieuwer vereisen en .pod quadlet-bestanden versie 5.0 vereisen. Voer podman --version uit voordat u een voorbeeld uit de upstream-documentatie kopieert.

Elke rootless-gebruiker heeft een bereik aan ondergeschikte ID's nodig:

grep "$USER" /etc/subuid /etc/subgid

Een gebruiker die is aangemaakt via adduser op Ubuntu krijgt automatisch een bereik toegewezen. Een gebruiker die is aangemaakt via useradd -M of via een configuratietool heeft dit vaak niet, wat resulteert in de volgende foutmelding:

Error: cannot find UID/GID for user deploy: no subuid ranges found for user "deploy" in /etc/subuid - check rootless mode in man pages.

Wijs een bereik toe en reset vervolgens de opslag van die gebruiker zodat de nieuwe mapping wordt gebruikt:

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy
podman system migrate

Nog een verrassing bij de eerste uitvoering: Podman gaat niet standaard uit van Docker Hub. Een korte imagenaam wordt opgelost aan de hand van unqualified-search-registries in /etc/containers/registries.conf, en in een script zonder gekoppelde terminal mislukt de pull met short-name resolution enforced but cannot prompt without a TTY. Gebruik altijd de volledige naam. Gebruik docker.io/library/nginx:1.27 in plaats van nginx.

Wat rootless containers u daadwerkelijk opleveren op een gehuurde server

Een rootless container draait binnen een user namespace, een kernel-functionaliteit die een proces een eigen, private toewijzing van user ID's geeft. Binnen de namespace is de superuser van de container UID (user ID) 0. Buiten de namespace, op uw VPS, is datzelfde proces uw normale inloggebruiker. Root in de container is geen root op de host.

Dat is de werkelijke omvang van het voordeel. Een image die vereist dat deze als root draait, een webapplicatie met een remote code execution-kwetsbaarheid, of een escape die afhankelijk is van UID 0 buiten de container: al deze scenario's resulteren in het verkrijgen van de rechten van uw ongeprivilegieerde gebruiker in plaats van die van de machine. Wat rootless niet doet, is u beschermen tegen kernel-bugs, en het beschermt uw eigen bestanden niet, omdat het ontsnapte proces als u draait en alles kan lezen wat u kunt lezen.

Docker kan ook rootless draaien. dockerd-rootless-setuptool.sh install stelt een daemon per gebruiker in en dit werkt goed. Het verschil zit in waar de standaardinstelling naar wijst. Bij Podman krijgt u rootless zonder erom te vragen, waardoor uw eerste foutmelding een container is die poort 80 niet kan binden, in plaats van een service die stilletjes twee jaar lang als root heeft gedraaid.

Waarom zijn mijn volumebestanden eigendom van UID 100999?

Dit komt door diezelfde user namespace. Container UID 0 wordt gemapt naar uw host UID. Container UID 1 wordt gemapt naar de eerste ID in uw subuid-bereik, en vanaf daar telt het op. Bij een bereik dat begint bij 100000, komt container UID 1000 op de host terecht als 100999.

mkdir -p "$PWD/data"
podman run --rm --user 1000 -v "$PWD/data:/data" docker.io/library/alpine:3 sh -c 'id -u; touch /data/f'
ls -ln "$PWD/data"

De container geeft 1000 weer. De listing op de host toont eigenaar 100999, omdat 100000 plus 1000 min 1 gelijk is aan 100999. Er is niets defect en een standaard chown zal dit niet oplossen, omdat uw niet-geprivilegieerde gebruiker buiten de namespace geen eigendomsrechten van bestanden kan wijzigen.

Er zijn vier oplossingen:

  • podman unshare chown 1000:1000 "$PWD/data" voert de chown uit binnen dezelfde user namespace, waar de getallen de betekenis hebben die ze voor de container hebben.
  • -v "$PWD/data:/data:U" vraagt Podman om het eigendom van de bronmap voor u te corrigeren. Gebruik dit op een nieuwe map, niet op data die voor u van belang is.
  • --userns=keep-id mapt uw host UID naar dezelfde UID binnen de container, zodat nieuwe bestanden als uw eigendom worden aangemaakt.
  • Een named volume zoals -v appdata:/data omzeilt de vraag, omdat Podman dit aanmaakt binnen uw eigen opslag met het eigendom al correct ingesteld.

Als u hier in Docker tegenaan bent gelopen, is het hetzelfde probleem een laag hoger. De PUID en PGID variabelen die veel images aanbieden stellen de UID in die het proces binnen de container gebruikt, en onder rootless Podman wordt die UID vervolgens een tweede keer gemapt. PUID=1000 binnen een rootless container schrijft nog steeds hostbestanden die eigendom zijn van 100999. Kies de getallen met die tweede mapping in gedachten, of verplaats de data naar een named volume en maak u er verder geen zorgen over.

Nog twee opmerkingen over mounts. De :z en :Z flags die u in voorbeelden voor Fedora en RHEL ziet, zijn SELinux relabel-opties; Ubuntu gebruikt AppArmor, dus daar doen ze niets. Rootless Podman kan bovendien geen hostmap mounten die uw gebruiker niet kan lezen; dit is het beoogde gedrag en geen fout.

Waarom weigert rootless Podman poort 80 te publiceren?

Omdat het binden van een poort onder 1024 een privilege vereist waarover uw gebruiker niet beschikt. De foutmelding benoemt de oplossing:

Error: rootlessport cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024), or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80: bind: permission denied

Er zijn twee werkbare oplossingen. Verlaag de drempel voor de gehele host:

echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-podman.conf
sudo sysctl --system
sysctl net.ipv4.ip_unprivileged_port_start

Het laatste commando hoort 80 terug te geven. Wees u bewust van wat deze instelling doet: elke gebruiker op de machine kan nu poort 80 en 443 binden, niet alleen de gebruiker die de containers uitvoert. Op een VPS met één beheerder is dit een acceptabel compromis. Op een server met accounts van anderen is dit onveilig. De andere oplossing is publiceren op 8080 en daar een reverse proxy voor plaatsen; dit is ook de plek waar u certificaten uitgegeven en vernieuwd door certbot op nginx wilt hebben.

Rootless publiceren verandert ook wat uw applicatie ziet. Podman 4.x gebruikt standaard slirp4netns met de rootlesskit port handler, waardoor doorgestuurde verbindingen aankomen met een herschreven bronadres. Hierdoor registreert het toegangslogboek elke bezoeker als 10.0.2.100. Podman 5.0 heeft de standaardinstelling gewijzigd naar pasta, wat het werkelijke clientadres behoudt. Op 4.x herstelt --network slirp4netns:port_handler=slirp4netns het juiste bronadres, zij het met een zekere impact op de doorvoersnelheid.

Er is hier één positieve verrassing. Een rootless gepubliceerde poort is een gewone listening socket die eigendom is van een normaal proces, waardoor de input-regels van uw firewall hierop van toepassing zijn. Docker publiceert poorten door NAT-regels (network address translation) te schrijven, aangevuld met eigen forwarding-acceptaties; dit is precies de reden waarom een gepubliceerde Docker-poort de ufw-regel negeert waarvan u dacht dat deze de poort blokkeerde. Rootful Podman gebruikt vergelijkbare technieken en erft hetzelfde probleem. Rootless doet dit niet.

Werken mijn Docker Compose-bestanden nog onder Podman?

Grotendeels wel, via twee verschillende routes. De eerste is podman-compose, een afzonderlijke implementatie die hetzelfde bestand leest en de Podman CLI aanstuurt:

sudo apt install -y podman-compose
podman-compose up -d
podman ps

De tweede is de echte Docker Compose die communiceert met de Docker-compatibele API van Podman via een socket per gebruiker:

systemctl --user enable --now podman.socket
export DOCKER_HOST="unix:///run/user/$(id -u)/podman/podman.sock"
docker compose up -d
podman ps

docker compose ps en podman ps zouden dezelfde containers moeten weergeven, omdat er slechts één set containers is. Naamresolutie werkt ook: de standaard netwerk-backend van Podman, netavark, voert aardvark-dns uit, waardoor containers op een door de gebruiker gedefinieerd netwerk elkaar op naam kunnen vinden.

De randgevallen zijn reëel. Alles wat /var/run/docker.sock mount, moet worden verwezen naar de Podman-socket of worden verwijderd. network_mode: host gedraagt zich anders onder een user namespace. depends_on met condition: service_healthy wordt ongelijkmatig ondersteund in verschillende versies van podman-compose. restart: always overleeft een herstart niet uit zichzelf, wat in de volgende sectie wordt opgelost. Compose blijft een goede manier om een stack met meerdere containers in één bestand te beschrijven, en onder Podman fungeert het als een vertaallaag. Voor een stack die u jarenlang wilt behouden, kunt u deze beter converteren naar quadlets en één abstractie onderhouden in plaats van twee.

Pods: het concept waar Docker geen antwoord op heeft

Een pod is een groep containers die één netwerk-namespace delen. Podman start een kleine infra-container om die namespace open te houden, waarna de leden elkaar kunnen bereiken op 127.0.0.1 zonder dat er een door de gebruiker gedefinieerd netwerk of service discovery aan te pas komt.

podman pod create --name app -p 8080:80
podman run -d --pod app --name app-cache docker.io/library/redis:7
podman run -d --pod app --name app-web docker.io/library/nginx:1.27
podman pod ps
podman ps --pod

podman pod ps zou de pod Running met drie containers moeten tonen, inclusief de infra-container. De web-container bereikt Redis nu op 127.0.0.1:6379 in plaats van op app-cache:6379. Uit de gedeelde namespace volgen twee regels: publiceer poorten op de pod en nooit op een lid, en geen twee leden mogen op dezelfde poort luisteren.

Dit is het Kubernetes-model, en Podman sluit hier nauw bij aan. podman kube generate app > app.yaml schrijft een Kubernetes-manifest op basis van wat er momenteel draait (in oudere pakketten heet dit podman generate kube), en podman kube play app.yaml maakt dit opnieuw aan op een andere host. Quadlet heeft een .kube-unit-type dat zo'n bestand uitvoert als een systemd-service. Het is een wezenlijk andere manier om services te groeperen, en het is de belangrijkste reden om voor Podman te kiezen als Kubernetes ergens in uw toekomstplannen voorkomt.

Automatisch starten zonder daemon: quadlet-units

Quadlet is een systemd-generator. Het zet een kort bestand met een beschrijving van een container bij het opstarten om in een echte systemd-service. Bestanden worden geplaatst in ~/.config/containers/systemd/ voor een rootless gebruiker, of in /etc/containers/systemd/ voor root.

~/.config/containers/systemd/caddy.container:

[Unit]
Description=Caddy web server
After=network-online.target

[Container]
Image=docker.io/library/caddy:2
ContainerName=caddy
PublishPort=8080:80
Volume=caddy-data.volume:/data
Environment=TZ=UTC
AutoUpdate=registry

[Service]
Restart=always
MemoryMax=512M

[Install]
WantedBy=default.target

~/.config/containers/systemd/caddy-data.volume kan bijna leeg zijn, aangezien de sectiekop het volume aanmaakt:

[Volume]
systemctl --user daemon-reload
systemctl --user start caddy
systemctl --user status caddy
journalctl --user -u caddy -n 50

De servicenaam is afgeleid van de bestandsnaam: caddy.container wordt caddy.service. Voer systemctl --user enable caddy niet uit. Gegenereerde units kunnen niet worden ingeschakeld en systemd geeft als antwoord Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated. De sectie [Install] start de container bij het opstarten, en daemon-reload regenereert de unit nadat u het bestand heeft bewerkt.

Nu de instelling waar bijna iedereen tegenaan loopt:

sudo loginctl enable-linger deploy
loginctl show-user deploy --property=Linger

Verwacht Linger=yes. Zonder linger breekt systemd de volledige gebruikerssessie af zodra uw laatste SSH-verbinding wordt verbroken; hierdoor stopt elke rootless container en start deze niet opnieuw op bij een reboot. Containers die verdwijnen wanneer u uitlogt, hebben altijd deze oorzaak.

Omdat de container het hoofdproces is van een normale service-unit, zijn de eigen besturingselementen van systemd direct van toepassing. MemoryMax= en CPUQuota= in de sectie [Service] gedragen zich precies zoals bij elke andere service die u met systemd beheert. Dit vereist cgroup v2 (control group versie 2), wat Ubuntu standaard gebruikt sinds 22.04. Controleer dit met podman info | grep -i cgroup.

Updates hebben een bijbehorend mechanisme. AutoUpdate=registry plus systemctl --user enable --now podman-auto-update.timer controleert de registry op een nieuwere image met dezelfde tag, herstart de unit en rolt terug naar de vorige image als de nieuwe container niet start. Voer eerst podman auto-update --dry-run uit om te zien wat er zou veranderen. Het oudere commando podman generate systemd bestaat nog steeds, maar is verouderd; schrijf daarom quadlets voor alles wat nieuw is.

Waar de docker-alias wel en niet werkt

sudo apt install -y podman-docker
sudo touch /etc/containers/nodocker
docker ps

podman-docker installeert een /usr/bin/docker-wrapper die Podman aanroept. Zonder het nodocker-bestand print elke aanroep eerst Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.. De wrapper dekt de commando's die u dagelijks gebruikt: run, ps, logs, exec, build, pull, push, inspect, cp, volume, network.

Wat niet wordt overgenomen is een kortere, maar belangrijkere lijst. Swarm-modus heeft geen equivalent, dus een Swarm-stack kan nergens landen. Tools die communiceren met de Docker-socket vereisen dat de Podman-socket wordt geëxporteerd, en sommige merken het verschil alsnog; de Docker-provider van Traefik werkt wanneer deze naar /run/user/<uid>/podman/podman.sock wijst, terwijl Watchtower geen plek heeft omdat podman auto-update die taak uitvoert. Opslag is gescheiden, dus Podman kan geen images zien die u al met Docker heeft binnengehaald, en podman images op een drukke Docker-host begint leeg.

Een draaiende stack stapsgewijs migreren

  1. Maak de ongeprivilegieerde gebruiker aan of kies er een die eigenaar wordt van de containers en bevestig dat deze een bereik heeft in /etc/subuid.
  2. Haal alle images uit een registry opnieuw op met volledig gekwalificeerde namen. Podman heeft een eigen image-opslag en leest de opslag van Docker niet.
  3. Verplaats lokaal gebouwde images met docker save app:1.4 | podman load.
  4. Stop de Docker-container, kopieer de inhoud van elk volume uit /var/lib/docker/volumes/<name>/_data en corrigeer vervolgens het eigenaarschap met podman unshare chown -R 1000:1000 <path>.
  5. Los de poortkwestie op: publiceer boven 1024 achter een reverse proxy of stel net.ipv4.ip_unprivileged_port_start in.
  6. Schrijf één quadlet-bestand per container, voer systemctl --user daemon-reload uit en start elke service.
  7. Voer sudo loginctl enable-linger <user> uit, herstart de VPS, log opnieuw in en controleer of podman ps elke service weer vermeldt.

De twee engines delen niets: ze hebben gescheiden image-opslag en gescheiden netwerken. U kunt beide engines tegelijkertijd draaien tijdens de migratie; het enige conflictpunt is een host-poortnummer. Verplaats één service, monitor deze een dag en verplaats vervolgens de volgende.

Podman versus Docker: wat hoort er op uw VPS?

Blijf bij Docker als uw stack in compose-bestanden staat die ook door anderen worden onderhouden, of als u afhankelijk bent van tooling die communiceert met de Docker-socket. Compatibiliteit met wat anderen schrijven is een reële eigenschap, en Docker biedt daar meer van. Een team waarvan alle laptops Docker draaien, haalt ook concreet voordeel uit het draaien van dezelfde engine in productie.

Stap over naar Podman als de VPS een handvol services draait die u volledig zelf beheert, of als u wilt dat elke applicatie onder een eigen niet-geprivilegieerde gebruiker draait, zonder enige docker-groep op de server. Distributie-afstemming telt ook: RHEL en zijn rebuilds leveren Podman als de ondersteunde engine; op die systemen is Podman dus de weg met de minste verrassingen. Als u al alles beheert met systemd-units, zullen quadlets aanvoelen als een ontbrekend puzzelstuk in plaats van een nieuwe tool om te leren.

Eén tussenoptie is het benoemen waard. Rootful Podman gedraagt zich grotendeels als Docker, behoudt het docker-commando via de wrapper en verwijdert nog steeds de altijd actieve daemon. Het geeft echter het rootless-aspect op, wat juist het deel is dat uw beveiligingspositie verandert; beschouw dit dus als een tussenstation.

Als u nog bezig bent met het bouwen van uw eerste containerhost, is de Docker-installatie en hardening-route op een nieuwe VPS de kortste weg, en die kennis is nooit verloren. Images en volumes zijn dezelfde objecten onder beide engines, dus een latere overstap verandert alleen de manier waarop uw services worden beheerd en verder vrijwel niets.

FAQ

Is Podman een directe vervanger voor Docker?

Voor de commando's die u invoert, komt het aardig in de buurt. Het installeren van podman-docker levert u een /usr/bin/docker-wrapper op, en run, ps, build, logs en exec gedragen zich op dezelfde wijze. Het is echter geen vervanging voor de daemon. Swarm heeft geen equivalent, tools die verbinding maken met /var/run/docker.sock moeten worden verwezen naar de Podman-socket per gebruiker, en images die door Docker zijn binnengehaald blijven onzichtbaar voor Podman omdat beide hun eigen opslag beheren.

Waarom stoppen mijn rootless Podman-containers wanneer ik uitlog via SSH?

Omdat systemd de gebruikerssessie, en daarmee elke gebruikersservice, beëindigt wanneer uw laatste sessie wordt gesloten. Voer sudo loginctl enable-linger <user> uit en controleer vervolgens of loginctl show-user <user> --property=Linger de waarde Linger=yes teruggeeft. Linger zorgt ervoor dat de systemd-instantie van die gebruiker actief blijft zonder actieve sessie; dit is tevens wat ervoor zorgt dat containers na een reboot weer opstarten.

Waarom zijn bestanden in mijn volume eigendom van UID 100999?

Rootless Podman mapt container-UID 0 naar uw hostgebruiker en mapt vervolgens container-UID 1 en hoger naar uw subuid-bereik. Bij een bereik dat begint bij 100000, wordt container-UID 1000 op de host 100999. Corrigeer dit vanuit de namespace met podman unshare chown 1000:1000 /path/to/data, mount bij de eerste uitvoering met de :U-vlag, of gebruik --userns=keep-id zodat container-UID's overeenkomen met die van uzelf.

Kan ik docker-compose.yml blijven gebruiken met Podman?

Ja, op twee manieren. podman-compose leest het bestand en stuurt de Podman CLI direct aan. Of schakel de compatibiliteitssocket in met systemctl --user enable --now podman.socket, stel DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock in en voer echte docker compose-commando's uit tegen deze socket. Houd rekening met mogelijke problemen bij network_mode: host, bij services die de Docker-socket mounten, en bij restart: always, waarvoor een quadlet-unit en linger vereist zijn om een reboot te overleven.

Maakt rootless containers daadwerkelijk veiliger?

Het elimineert één specifiek risico: een proces dat uit een rootless container ontsnapt, beschikt over de rechten van uw ongeprivilegieerde gebruiker in plaats van die van root. Dat is waardevol, en daarom heeft de root-equivalente docker-groep geen tegenhanger in rootless Podman. Het voorkomt geen kernel-kwetsbaarheden en beschermt geen bestanden die uw eigen gebruiker kan lezen; pas daarom de overige beveiligingsmaatregelen toe die u op elke server zou treffen.