SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-27

Podman o Docker su VPS: cosa cambia davvero

Podman usa container rootless senza daemon. Scopri cosa comporta su un VPS per file Compose, quadlet, porte sotto 1024 e proprieta dei volumi.

Cosa cambia effettivamente tra Podman e Docker

Podman e Docker eseguono le stesse immagini OCI (Open Container Initiative) su un VPS, quindi la scelta non dipende dal software che è possibile eseguire. La differenza riguarda il modello dei processi. Docker esegue un daemon con privilegi root che gestisce tutti i container, mentre il comando docker è un client leggero che chiede a quel daemon di eseguire le operazioni. Podman non usa un daemon: podman run avvia il container come processo figlio del processo che lo richiama, con il proprio utente senza privilegi.

Tutto il resto deriva da questo aspetto. L'avvio automatico diventa compito di systemd anziché del daemon. La proprietà dei volumi passa attraverso uno user namespace, quindi il proprietario visualizzato con ls -l sull'host non è quello visualizzato dal container. Le porte inferiori a 1024 non possono essere associate finché non si modifica un'impostazione del kernel. La CLI docker (interfaccia a riga di comando) continua a funzionare tramite un wrapper, fino a quando un'operazione richiede il socket Docker.

Nessun daemon: cosa viene realmente eseguito quando si avvia un container

Su un host Docker, pstree -a mostra dockerd come root, containerd accanto a esso e un containerd-shim-runc-v2 per ogni container in esecuzione. L'applicazione è figlia di quello shim, che a sua volta è figlio del PID 1. Nulla collega il container alla shell che lo ha avviato. Se si arresta il daemon, si perde il piano di controllo di tutti i container presenti sull'host e, con l'impostazione predefinita live-restore disattivata, systemctl restart docker riavvia anche i container.

Podman non ha un processo equivalente. Quando si avvia un container, viene creato un solo processo conmon (monitor del container) che mantiene il processo principale del container ed è di proprietà dell'utente che ha eseguito il comando.

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 dovrebbe elencare conmon in esecuzione con il proprio utente di accesso e non come root, mentre curl dovrebbe restituire 200. Poiché nessun servizio centrale gestisce il container, sudo apt upgrade podman non arresta nulla di ciò che è già in esecuzione e il crash del monitor di un container non può trascinare con sé gli altri.

L'assenza del daemon comporta anche un limite. Dopo un riavvio, nessuno avvia i container. --restart=always di Docker è una promessa mantenuta dal daemon all'avvio, mentre Podman la sostituisce con systemd, che è lo scopo della sezione sui quadlet riportata di seguito.

Il socket è l'altra parte della questione. /var/run/docker.sock è un endpoint API (application programming interface) di proprietà di root e qualsiasi processo che possa scrivervi può avviare un container privilegiato con il filesystem dell'host montato. Aggiungere un utente al gruppo docker concede a quell'utente privilegi root attraverso un percorso più indiretto; è utile leggere questo aspetto insieme a concedere a ogni account di servizio soltanto l'accesso necessario. Podman non espone alcun socket se non lo si richiede, e il socket ottenuto appartiene a un singolo utente in /run/user/<uid>/podman/podman.sock.

Installare Podman su Ubuntu 24.04 e verificare che rootless sia effettivo

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

Il pacchetto uidmap fornisce newuidmap e newgidmap. Sono gli helper setuid che consentono a un utente normale di utilizzare un intervallo di ID subordinati. Senza questi helper, i container rootless non si avviano. podman info dovrebbe stampare rootless: true.

Ubuntu 24.04 include Podman 4.9 e Debian 13 include Podman 5.x, secondo le verifiche effettuate ad agosto 2026. La differenza è importante, perché i file quadlet richiedono la versione 4.4 o successiva, mentre i file quadlet .pod richiedono la versione 5.0. Esegui podman --version prima di copiare un esempio dalla documentazione upstream.

Ogni utente rootless deve avere un intervallo di ID subordinati:

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

Un utente creato da adduser su Ubuntu riceve automaticamente un intervallo. Un utente creato da useradd -M o da uno strumento di configurazione spesso non lo riceve, e l'errore lo indica:

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

Assegna un intervallo, quindi reimposta lo storage di quell'utente in modo da utilizzare la nuova mappatura:

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

Un'altra sorpresa al primo avvio: Podman non presuppone Docker Hub. Un nome breve per un'immagine viene risolto in base a unqualified-search-registries in /etc/containers/registries.conf, e in uno script senza terminale associato il pull non riesce con short-name resolution enforced but cannot prompt without a TTY. Scrivi ogni volta il nome completo. Usa docker.io/library/nginx:1.27 invece di nginx.

Che cosa offrono realmente i container rootless su un server a noleggio

Un container rootless viene eseguito all'interno di uno user namespace, una funzionalità del kernel che assegna a un processo una mappatura privata degli ID utente. All'interno del namespace, il superuser del container è UID (user ID) 0. All'esterno, sul VPS, lo stesso processo è l'utente con cui hai effettuato il login. L'utente root del container non è root sull'host.

Questo è il reale vantaggio. Un'immagine che richiede l'esecuzione come root, un'applicazione web con una vulnerabilità di remote code execution o un'escape che dipende dall'essere UID 0 all'esterno: in tutti questi casi il processo ottiene i permessi dell'utente non privilegiato invece di quelli dell'intera macchina. Rootless non protegge dai bug del kernel e non protegge i tuoi file, perché il processo che esegue l'escape gira con il tuo stesso utente e può leggere tutto ciò che puoi leggere tu. L'unità isolata è importante almeno quanto la mappatura degli UID. Questo è più evidente nel caso di un jail FreeBSD, che racchiude un intero userland amministrato come una piccola macchina, anziché di un'immagine a livelli scaricata da un registry.

Anche Docker può essere eseguito in modalità rootless. dockerd-rootless-setuptool.sh install configura un daemon per utente e funziona bene. La differenza riguarda il comportamento predefinito. Con Podman ottieni rootless senza doverlo richiedere. Il primo problema che incontri è quindi un container che non può collegarsi alla porta 80, invece di un servizio eseguito silenziosamente come root per due anni.

Perché i file dei miei volumi appartengono a UID 100999?

La causa è lo stesso user namespace. L'UID 0 del container viene mappato sull'UID dell'host. L'UID 1 del container viene mappato sul primo ID dell'intervallo subuid e gli ID successivi aumentano progressivamente. Con un intervallo che inizia da 100000, l'UID 1000 del container corrisponde all'UID 100999 sull'host.

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"

Il container stampa 1000. L'elenco sull'host mostra come proprietario 100999, perché 100000 più 1000 meno 1 fa 100999. Non c'è alcun problema e un semplice chown non risolve la situazione, perché l'utente senza privilegi non può modificare la proprietà dei file al di fuori dello user namespace.

È possibile procedere in 4 modi:

  • podman unshare chown 1000:1000 "$PWD/data" esegue chown nello stesso user namespace, dove i numeri hanno il significato previsto dal container.
  • -v "$PWD/data:/data:U" chiede a Podman di correggere la proprietà della directory sorgente. Usarlo su una directory nuova, non su dati importanti.
  • --userns=keep-id mappa l'UID dell'host sullo stesso UID all'interno del container, così i nuovi file risultano di proprietà dell'utente.
  • Un volume con nome, ad esempio -v appdata:/data, evita il problema, perché Podman lo crea nel proprio storage con la proprietà già corretta.

Se hai già gestito questo problema con Docker, si tratta della stessa situazione a un livello di mapping successivo. Le variabili PUID e PGID esposte da molte immagini impostano l'UID utilizzato dal processo all'interno del container e, con Podman rootless, tale UID viene mappato una seconda volta. Anche PUID=1000 all'interno di un container rootless scrive sull'host file appartenenti all'UID 100999. Scegli i numeri tenendo conto anche del secondo mapping oppure sposta i dati in un volume con nome e non considerare più il problema.

Altri due aspetti riguardano i mount. I flag :z e :Z presenti negli esempi per Fedora e RHEL sono opzioni di rietichettatura SELinux, mentre Ubuntu usa AppArmor; pertanto su Ubuntu non hanno alcun effetto. Inoltre, Podman rootless non può montare una directory dell'host che l'utente non può leggere. Questo comportamento è previsto e non indica un malfunzionamento.

Perché rootless Podman rifiuta di pubblicare la porta 80?

Perché il binding di una porta inferiore a 1024 richiede un privilegio che l'utente non possiede. L'errore indica la correzione:

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

Sono possibili due soluzioni. Abbassare la soglia per l'intero 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

L'ultimo comando dovrebbe restituire 80. È importante capire cosa comporta questa impostazione: ogni utente della macchina può ora eseguire il binding sulle porte 80 e 443, non soltanto l'utente che esegue i container. Su un VPS amministrato da una sola persona, questo compromesso può essere accettabile. Su un server che ospita gli account di altre persone, invece, non lo è. L'altra soluzione consiste nel pubblicare sulla porta 8080 e mettere un reverse proxy davanti al servizio. È anche il punto in cui vuoi emettere e rinnovare i certificati con certbot su nginx.

La pubblicazione in modalità rootless modifica anche ciò che vede l'applicazione. Podman 4.x usa slirp4netns con il gestore delle porte rootlesskit per impostazione predefinita. Le connessioni inoltrate arrivano con un indirizzo sorgente riscritto. Di conseguenza, il log degli accessi registra ogni visitatore come 10.0.2.100. Podman 5.0 ha modificato l'impostazione predefinita in pasta, che conserva l'indirizzo reale del client. Su 4.x, --network slirp4netns:port_handler=slirp4netns ripristina l'indirizzo sorgente reale, con una certa riduzione del throughput.

Qui c'è un aspetto positivo. Una porta pubblicata in modalità rootless è un normale socket in ascolto, di proprietà di un processo normale. Pertanto, le regole di input del firewall si applicano anche a questa porta. Docker pubblica le porte scrivendo regole NAT (network address translation) e regole proprie che consentono l'inoltro. È proprio questo il motivo per cui una porta Docker pubblicata ignora la regola ufw che pensavi la bloccasse. Podman rootful usa un meccanismo simile e presenta lo stesso rischio. La modalità rootless no.

I miei file Docker Compose continuano a funzionare con Podman?

Nella maggior parte dei casi, seguendo due approcci diversi. Il primo è podman-compose, un'implementazione separata che legge lo stesso file e usa la CLI di Podman:

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

Il secondo consiste nell'usare Docker Compose reale tramite l'API compatibile con Docker di Podman, attraverso un socket specifico per l'utente:

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 e podman ps dovrebbero elencare gli stessi container, perché esiste un solo insieme di container. Anche la risoluzione dei nomi funziona: il backend di rete predefinito di Podman, netavark, esegue aardvark-dns, quindi i container appartenenti a una rete definita dall'utente possono trovarsi tra loro tramite nome.

Esistono però alcune limitazioni concrete. Qualsiasi configurazione che monta /var/run/docker.sock deve essere indirizzata al socket di Podman oppure rimossa. network_mode: host si comporta in modo diverso all'interno di uno user namespace. Il supporto a depends_on con condition: service_healthy varia tra le versioni di podman-compose. restart: always non viene ripristinato automaticamente dopo un riavvio; la sezione successiva risolve questo problema. Compose rimane un buon modo per descrivere uno stack con più container in un singolo file e, con Podman, funge da livello di conversione. Per uno stack che prevedi di mantenere per anni, convertilo in quadlet e gestisci una sola astrazione invece di due.

Pod: il concetto a cui Docker non dà una risposta

Un pod è un gruppo di container che condividono un unico namespace di rete. Podman avvia un piccolo container infra per mantenere attivo quel namespace; i membri possono quindi raggiungersi tramite 127.0.0.1, senza una rete definita dall’utente e senza ricorrere al service discovery.

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 dovrebbe mostrare il pod Running con tre container, incluso il container infrastrutturale. Il container web ora raggiunge Redis tramite 127.0.0.1:6379 invece che tramite app-cache:6379. Dal namespace condiviso derivano due regole: pubblicare le porte sul pod e mai su un membro, e non consentire a due membri di rimanere in ascolto sulla stessa porta.

Questo è il modello di Kubernetes, e Podman lo adotta pienamente. podman kube generate app > app.yaml genera un manifest Kubernetes dalla configurazione in esecuzione (nei pacchetti meno recenti il comando è indicato come podman generate kube), mentre podman kube play app.yaml lo ricrea su un altro host. Quadlet dispone di un tipo di unità .kube che esegue un file di questo tipo come servizio systemd. È un modo realmente diverso di raggruppare i servizi e rappresenta il motivo più forte per scegliere Podman se Kubernetes rientra nei propri piani futuri.

Avvio automatico senza demone: unità quadlet

Quadlet è un generatore di systemd. Trasforma un breve file che descrive un container in un vero servizio systemd all'avvio. I file si trovano in ~/.config/containers/systemd/ per un utente rootless oppure in /etc/containers/systemd/ per 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 può essere quasi vuoto, perché è l'intestazione della sezione a creare il volume:

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

Il nome del servizio deriva dal nome del file: caddy.container diventa caddy.service. Non eseguire systemctl --user enable caddy. Le unità generate non possono essere abilitate e systemd risponde Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated. La sezione [Install] avvia il container all'avvio, mentre daemon-reload rigenera l'unità dopo la modifica del file.

Ora analizziamo l'impostazione che causa problemi a quasi tutti:

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

Attendi Linger=yes. Senza il linger, systemd arresta l'intera sessione utente quando si chiude l'ultima connessione SSH. Di conseguenza, tutti i container rootless si arrestano e nessuno di essi torna attivo al successivo avvio. Se i container scompaiono quando esci dalla sessione, questa è sempre la causa.

Poiché il container è il processo principale di una normale unità di servizio, i controlli di systemd si applicano direttamente. MemoryMax= e CPUQuota= nella sezione [Service] si comportano esattamente come per qualsiasi altro servizio che limiti con systemd. È necessario cgroup v2 (versione 2 del control group), utilizzato da Ubuntu per impostazione predefinita da 22.04. Verifica con podman info | grep -i cgroup.

Gli aggiornamenti dispongono di un meccanismo corrispondente. AutoUpdate=registry insieme a systemctl --user enable --now podman-auto-update.timer controlla nel registry la presenza di un'immagine più recente con lo stesso tag, riavvia l'unità e ripristina l'immagine precedente se il nuovo container non riesce ad avviarsi. Esegui prima podman auto-update --dry-run per vedere quali modifiche apporterebbe. Il comando precedente podman generate systemd esiste ancora ed è deprecato, quindi usa i quadlet per tutto ciò che è nuovo.

Dove l'alias docker funziona e dove no

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

podman-docker installa un wrapper /usr/bin/docker che richiama Podman. Senza il file nodocker, ogni chiamata stampa prima Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.. Il wrapper copre i comandi che si usano ogni giorno: run, ps, logs, exec, build, pull, push, inspect, cp, volume, network.

Le funzionalità che non vengono trasferite sono meno numerose, ma più importanti. La modalità Swarm non ha un equivalente, quindi uno stack Swarm non può essere eseguito. Gli strumenti che comunicano con il socket Docker richiedono l'esportazione del socket Podman, e alcuni rilevano comunque la differenza; il provider Docker di Traefik funziona se viene indirizzato a /run/user/<uid>/podman/podman.sock, mentre Watchtower non ha un equivalente, perché podman auto-update svolge quella funzione. Lo storage è separato, quindi Podman non può vedere le immagini già scaricate con Docker e podman images su un host Docker occupato è inizialmente vuoto.

Migrazione di uno stack in esecuzione, passo dopo passo

  1. Creare o scegliere l'utente non privilegiato che sarà proprietario dei container e verificare che disponga di un intervallo in /etc/subuid.
  2. Eseguire nuovamente il pull di tutto ciò che proviene da un registry, usando nomi completi. Podman dispone di un proprio archivio delle immagini e non legge quello di Docker.
  3. Trasferire le immagini compilate localmente con docker save app:1.4 | podman load.
  4. Arrestare il container Docker, copiare il contenuto di ogni volume da /var/lib/docker/volumes/<name>/_data, quindi correggere la proprietà con podman unshare chown -R 1000:1000 <path>.
  5. Decidere come gestire le porte: pubblicare una porta superiore a 1024 dietro un reverse proxy oppure impostare net.ipv4.ip_unprivileged_port_start.
  6. Scrivere un file quadlet per ogni container, eseguire systemctl --user daemon-reload e avviare ogni servizio.
  7. Eseguire sudo loginctl enable-linger <user>, riavviare il VPS, accedere nuovamente e verificare che podman ps elenchi di nuovo tutti i servizi.

I due motori non condividono nulla: hanno archivi delle immagini e reti distinti. È quindi possibile eseguirli entrambi durante la migrazione; l'unica risorsa per cui possono entrare in conflitto è il numero di una porta host. Spostare un servizio, monitorarlo per un giorno, quindi passare al successivo.

Podman e Docker: quale usare sul tuo VPS?

Mantieni Docker se il tuo stack è definito in file Compose gestiti anche da altre persone oppure se dipendi da strumenti che comunicano con il socket Docker. La compatibilità con ciò che scrive il resto dell’ecosistema è una funzionalità concreta, e Docker ne offre di più. Un team i cui laptop usano tutti Docker ottiene inoltre un vantaggio concreto eseguendo lo stesso engine in produzione.

Passa a Podman se sul VPS esegui pochi servizi che controlli dall’inizio alla fine oppure se vuoi eseguire ogni applicazione con un proprio utente senza privilegi, senza alcun gruppo docker sul sistema. Anche l’allineamento con la distribuzione è importante: RHEL e le sue ricostruzioni distribuiscono Podman come engine supportato, quindi su questi sistemi Podman è la scelta che comporta meno sorprese. Se vuoi comunque usare Docker su uno di questi host, la procedura dnf su Rocky Linux e AlmaLinux inizia rimuovendo il wrapper podman-docker che già gestisce il comando docker. Se supervisioni già tutto il resto con unità systemd, i quadlet sembreranno un componente mancante che finalmente arriva, non un nuovo strumento da imparare.

Vale la pena citare anche un’opzione intermedia. Podman rootful si comporta in modo simile a Docker, mantiene il comando docker tramite il wrapper e rimuove comunque il daemon sempre in esecuzione. Rinuncia però alla modalità rootless, che è l’aspetto che modifica il livello di sicurezza, quindi consideralo una tappa intermedia.

Se stai ancora creando il tuo primo host per container, la procedura di configurazione e hardening di Docker su un VPS nuovo è la strada più breve, e nessuna di queste conoscenze andrà sprecata. Immagini e volumi sono gli stessi oggetti in entrambi gli engine, quindi una migrazione modifica il modo in cui i servizi vengono supervisionati e poco altro.

FAQ

Podman è un sostituto drop-in di Docker?

Per i comandi che si digitano, quasi. L'installazione di podman-docker fornisce un wrapper /usr/bin/docker, mentre run, ps, build, logs e exec funzionano nello stesso modo. Non sostituisce il daemon. Swarm non ha un equivalente, gli strumenti che si connettono a /var/run/docker.sock devono puntare al socket Podman per-utente e le immagini scaricate da Docker restano invisibili a Podman perché i due strumenti usano archivi separati.

Perché i container Podman rootless si arrestano quando chiudo la sessione SSH?

Perché systemd arresta la sessione dell'utente e, con essa, tutti i relativi servizi utente quando si chiude l'ultima sessione di accesso. Esegui sudo loginctl enable-linger <user>, quindi verifica che loginctl show-user <user> --property=Linger restituisca Linger=yes. Linger mantiene in esecuzione l'istanza systemd dell'utente anche senza una sessione attiva. È inoltre ciò che consente ai container di riavviarsi dopo un reboot.

Perché i file nel mio volume appartengono a UID 100999?

Podman rootless associa l'UID 0 del container all'utente host, quindi associa l'UID 1 del container e gli UID successivi all'intervallo subuid dell'utente. Con un intervallo che inizia da 100000, l'UID 1000 del container diventa 100999 sull'host. Correggi il proprietario dall'interno del namespace con podman unshare chown 1000:1000 /path/to/data, esegui il mount usando il flag :U al primo avvio oppure usa --userns=keep-id per fare corrispondere gli UID del container a quelli dell'utente.

Posso continuare a usare docker-compose.yml con Podman?

Sì, in due modi. podman-compose legge il file e gestisce direttamente la CLI di Podman. In alternativa, abilita il socket di compatibilità con systemctl --user enable --now podman.socket, imposta DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock ed esegui il vero docker compose tramite quel socket. Possono sorgere problemi con network_mode: host, con i servizi che montano il socket Docker e con restart: always, che per sopravvivere a un reboot richiede un'unità quadlet e linger.

Il funzionamento rootless rende davvero i container più sicuri?

Elimina un rischio specifico: un processo che evade da un container rootless dispone dei permessi dell'utente non privilegiato, anziché dei permessi di root. È un vantaggio importante. Per questo il gruppo docker, equivalente a root, non ha un corrispondente in Podman rootless. Questa modalità non impedisce le vulnerabilità del kernel e non protegge i file che l'utente può leggere direttamente. Mantieni quindi tutte le altre misure di hardening che applicheresti a qualsiasi server.