SSD Nodes Learn 🎉 VPS da $5.50/mese
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-13

Podman o Docker su un VPS: cosa cambia davvero

Podman usa container senza daemon e rootless per impostazione predefinita. Scopri cosa cambia su un VPS per Compose, Quadlet, porte 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 riguarda quali applicazioni sia possibile eseguire. La differenza riguarda il modello dei processi. Docker esegue un demone con privilegi root che gestisce tutti i container, mentre il comando docker è un client minimale che chiede al demone di eseguire le operazioni. Podman non usa un demone: podman run avvia il container come processo figlio del processo che lo ha chiamato, usando il proprio utente senza privilegi.

Tutto il resto deriva da questo fatto. L'avvio automatico diventa un compito di systemd anziché del demone. La proprietà dei volumi passa attraverso uno user namespace, quindi il proprietario visualizzato sull'host con ls -l 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 della riga di comando) continua a funzionare tramite un wrapper, fino a quando un'operazione richiede il socket Docker.

Nessun daemon: cosa viene eseguito realmente quando avvii 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 è un processo figlio di quello shim, che a sua volta è figlio del PID 1. Nulla collega il container alla shell da cui è stato avviato. Se arresti il daemon, perdi 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. Avvia un container e ottieni un solo processo conmon (monitor del container) che mantiene il processo principale del container, eseguito dall'utente che ha impartito 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 tuo utente di accesso e non come root, mentre curl dovrebbe stampare 200. Poiché nessun servizio centrale gestisce il container, sudo apt upgrade podman non arresta nulla di già in esecuzione e il crash del monitor di un container non può coinvolgere 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 è il tema della sezione successiva sui quadlet.

Il socket è l'altra parte della questione. /var/run/docker.sock è un endpoint API (application programming interface) di proprietà di root; 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 tramite un percorso più indiretto, come spiegato in concedere a ogni account di servizio solo l'accesso necessario. Podman non espone alcun socket se non lo richiedi e il socket che ottieni appartiene a un singolo utente indicato da /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, dati verificati ad agosto 2026. La differenza è importante: i file quadlet richiedono la versione 4.4 o successiva, mentre i file quadlet .pod richiedono la versione 5.0. Eseguire podman --version prima di copiare un esempio dalla documentazione upstream.

Ogni utente rootless richiede 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.

Assegnare un intervallo, quindi reimpostare lo storage di quell'utente per 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 presume di utilizzare Docker Hub. Un nome breve dell'immagine viene risolto in base a unqualified-search-registries in /etc/containers/registries.conf. In uno script senza un terminale associato, il pull fallisce con short-name resolution enforced but cannot prompt without a TTY. Specificare sempre il nome completo. Usare docker.io/library/nginx:1.27 invece di nginx.

Cosa offre realmente l'esecuzione di 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 propria mappatura privata degli user ID. All'interno del namespace, il superuser del container è UID (user ID) 0. All'esterno, sul tuo VPS, lo stesso processo è il tuo normale utente di accesso. root nel container non è root sull'host.

Questo è l'effettivo vantaggio. Un'immagine che richiede l'esecuzione come root, un'applicazione web con una vulnerabilità di remote code execution oppure un escape che dipende dall'essere UID 0 all'esterno: in tutti questi casi il processo ottiene i permessi del tuo utente senza privilegi, non quelli dell'intera macchina. L'esecuzione rootless non protegge dai bug del kernel e non protegge i tuoi file, perché il processo che esegue l'escape è in esecuzione con il tuo utente e può leggere tutto ciò che puoi leggere tu.

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 l'esecuzione rootless senza doverla richiedere, quindi il primo problema che incontri è un container che non può associare la porta 80, non un servizio che è stato eseguito in silenzio come root per due anni.

Perché i file del volume appartengono all'UID 100999?

La causa è lo stesso namespace utente. L'UID 0 del container viene mappato sull'UID dell'host. L'UID 1 del container viene mappato sul primo ID dell'intervallo subuid, quindi la numerazione prosegue da quel valore. Con un intervallo che inizia da 100000, l'UID 1000 del container diventa 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 mostra 1000. L'elenco sull'host mostra come proprietario 100999, perché 100000 più 1000 meno 1 è 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 del namespace.

Ci sono quattro soluzioni:

  • podman unshare chown 1000:1000 "$PWD/data" esegue chown nello stesso namespace utente, dove i numeri hanno il significato previsto dal container.
  • -v "$PWD/data:/data:U" chiede a Podman di correggere la proprietà della directory sorgente. Usalo 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 denominato come -v appdata:/data evita il problema, perché Podman lo crea nel proprio storage con la proprietà già corretta.

Se hai già incontrato questo problema con Docker, si tratta dello stesso problema a un livello diverso. Le variabili PUID e PGID esposte da molte immagini impostano l'UID usato dal processo nel container e, con Podman rootless, quell'UID viene mappato una seconda volta. PUID=1000 all'interno di un container rootless continua a scrivere file sull'host con proprietà 100999. Scegli i numeri tenendo conto di questa seconda mappatura oppure sposta i dati in un volume denominato e non devi più occupartene.

Altre due note sui mount. I flag :z e :Z presenti negli esempi per Fedora e RHEL sono opzioni per la rietichettatura SELinux. Ubuntu usa AppArmor, quindi lì non producono alcun effetto. Podman rootless non può inoltre montare una directory dell'host che l'utente non può leggere. Questo è il comportamento previsto, non un errore.

Perché Podman rootless 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. Ridurre 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 chiarire l'effetto di 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 gestito da un unico amministratore, questo compromesso è accettabile. Su un server che ospita gli account di altri utenti, non lo è. L'altra soluzione consiste nel pubblicare sulla porta 8080 e usare un reverse proxy davanti al servizio. È questa la configurazione da preferire anche per i certificati emessi e rinnovati da certbot su nginx.

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

C'è però un aspetto positivo. Una porta pubblicata in modalità rootless è un normale socket in ascolto, di proprietà di un processo non privilegiato. Le regole di input del firewall si applicano quindi a questa porta. Docker pubblica le porte scrivendo regole NAT (network address translation) e regole proprie che consentono l'inoltro. È esattamente 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 problema. La modalità rootless no.

I file Docker Compose continuano a funzionare con Podman?

Nella maggior parte dei casi, attraverso due modalità. La prima è podman-compose, un'implementazione separata che legge lo stesso file e utilizza la CLI di Podman:

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

La seconda consiste nell'usare Docker Compose reale tramite l'API compatibile con Docker di Podman, esposta su un socket per 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é l'insieme dei container è uno solo. Anche la risoluzione dei nomi funziona: il backend di rete predefinito di Podman, netavark, esegue aardvark-dns, quindi i container connessi a una rete definita dall'utente possono trovarsi tra loro tramite il nome.

Esistono tuttavia alcuni limiti concreti. Qualsiasi elemento che monta /var/run/docker.sock deve essere configurato per usare il socket di Podman oppure deve essere rimosso. network_mode: host si comporta in modo diverso all'interno di uno user namespace. Il supporto di depends_on con condition: service_healthy varia tra le versioni di podman-compose. restart: always non sopravvive autonomamente a un riavvio; la sezione successiva risolve questo problema. Compose resta un buon metodo per descrivere uno stack con più container in un singolo file e, con Podman, funge da livello di traduzione. 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 questo namespace; i membri comunicano quindi tra loro tramite 127.0.0.1, senza reti definite 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 infra. 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 singolo membro; inoltre, due membri non possono restare 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 a partire da ciò che è in esecuzione; nei pacchetti meno recenti il comando è indicato come podman generate kube. podman kube play app.yaml lo ricrea su un altro host. Quadlet dispone di un tipo di unità .kube che esegue questo file come servizio systemd. È un modo realmente diverso di raggruppare i servizi e rappresenta il motivo più importante per scegliere Podman se Kubernetes rientra anche solo nei piani futuri.

Avvio automatico senza un demone: unità quadlet

Quadlet è un generatore di systemd. Trasforma un file breve che descrive un container in un servizio systemd reale 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

È previsto Linger=yes. Senza linger, systemd termina l'intera sessione utente quando si chiude l'ultima connessione SSH. Di conseguenza, tutti i container rootless si arrestano e nessuno di loro viene riavviato all'avvio. Se i container scompaiono quando effettui il logout, questa è sempre la causa.

Poiché il container è il processo principale di una normale unità di servizio, i controlli propri di systemd si applicano direttamente. MemoryMax= e CPUQuota= nella sezione [Service] si comportano esattamente come per qualsiasi altro servizio gestito con systemd. È necessario cgroup v2 (control group versione 2), usato per impostazione predefinita da Ubuntu dalla versione 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 si avvia. Esegui prima podman auto-update --dry-run per verificare quali modifiche verrebbero applicate. Il comando precedente podman generate systemd è ancora disponibile, ma è deprecato; per le nuove configurazioni usa i quadlet.

Dove è disponibile l’alias docker e dove invece non lo è

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 comando stampa prima Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.. Il wrapper copre i comandi usati quotidianamente: run, ps, logs, exec, build, pull, push, inspect, cp, volume, network.

Le funzionalità non disponibili sono meno numerose, ma più rilevanti. Swarm mode 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 configurato per /run/user/<uid>/podman/podman.sock, mentre Watchtower non ha un equivalente, perché podman auto-update svolge questa funzione. Lo storage è separato, quindi Podman non può vedere le immagini già scaricate con Docker e podman images, su un host Docker utilizzato intensivamente, inizialmente è vuoto.

Migrazione di uno stack in esecuzione, passo dopo passo

  1. Crea o scegli l'utente non privilegiato proprietario dei container e verifica che disponga di un intervallo in /etc/subuid.
  2. Scarica nuovamente tutto ciò che proviene da un registry, usando nomi completamente qualificati. Podman dispone di un proprio archivio di immagini e non legge quello di Docker.
  3. Trasferisci le immagini create localmente con docker save app:1.4 | podman load.
  4. Arresta il container Docker, copia i contenuti di ogni volume da /var/lib/docker/volumes/<name>/_data, quindi correggi la proprietà con podman unshare chown -R 1000:1000 <path>.
  5. Risolvi il problema delle porte: pubblica una porta superiore a 1024 dietro un reverse proxy oppure configura net.ipv4.ip_unprivileged_port_start.
  6. Scrivi un file quadlet per ogni container, esegui systemctl --user daemon-reload e avvia ogni servizio.
  7. Esegui sudo loginctl enable-linger <user>, riavvia il VPS, accedi nuovamente e verifica che podman ps elenchi di nuovo tutti i servizi.

I due engine non condividono nulla: hanno archivi di immagini e reti separati. Puoi quindi eseguirli entrambi durante la migrazione. L'unico elemento per il quale possono entrare in conflitto è il numero di una porta dell'host. Sposta un servizio, monitoralo per un giorno, quindi passa al successivo.

Podman o Docker: quale scegliere sul tuo VPS?

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

Passa a Podman se il VPS esegue pochi servizi che controlli end-to-end o se vuoi eseguire ogni applicazione con un proprio utente non privilegiato, senza alcun gruppo docker sul sistema. Anche l’integrazione 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 già gestisci 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; consideralo quindi una soluzione di passaggio.

Se stai ancora configurando il tuo primo host per container, il percorso per configurare e mettere in sicurezza Docker su un VPS appena creato è la strada più breve e nessuna delle conoscenze acquisite 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 quasi nient’altro.

FAQ

Podman è un sostituto immediato di Docker?

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

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

Perché systemd arresta la sessione utente e tutti i relativi servizi utente quando si chiude l'ultimo 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 avviarsi nuovamente dopo un riavvio.

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

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

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. Potrebbero verificarsi problemi con network_mode: host, con i servizi che montano il socket Docker e con restart: always, che per sopravvivere a un riavvio 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 mantiene i permessi dell'utente non privilegiato invece di quelli di root. È un vantaggio importante e spiega perché il gruppo docker, equivalente a root, non ha un corrispondente in Podman rootless. Non impedisce le vulnerabilità del kernel e non protegge i file che il tuo stesso utente può leggere. Mantieni quindi tutte le altre misure di hardening che applicheresti su qualsiasi server.