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

Installare Docker su Rocky Linux e AlmaLinux

Installa Docker Engine con dnf e Compose su Rocky Linux e AlmaLinux. Risolvi il conflitto di Podman sul comando docker e i blocchi SELinux sui bind mount.

Installare Docker su Rocky Linux e AlmaLinux

Per installare Docker su Rocky Linux o AlmaLinux, aggiungere il repository dnf ufficiale di Docker, installare il motore con il plugin Compose e quindi abilitare il servizio. Sono quattro comandi e la procedura è identica su entrambe le distribuzioni, perché entrambe sono ricostruzioni di Red Hat Enterprise Linux (RHEL) e condividono la stessa struttura dei pacchetti. Anche CentOS Stream funziona allo stesso modo.

L'installazione è breve, quindi la maggior parte di questa guida descrive le differenze di Enterprise Linux (EL) rispetto a Ubuntu. Nell'immagine in uso, Podman potrebbe già gestire il comando docker. SELinux blocca i file montati con bind mount finché non ricevono l'etichetta corretta. Firewalld non filtra le porte pubblicate da Docker, quindi una porta del container può essere accessibile da Internet mentre firewall-cmd indica che non ci sono porte aperte.

Non usare lo script di installazione semplificata di Docker disponibile all'indirizzo get.docker.com. La documentazione ufficiale di Docker specifica che non è consigliato per la produzione. Lo script modifica la configurazione dei repository senza chiedere conferma e non può essere eseguito nuovamente in sicurezza per effettuare un aggiornamento. Aggiungere manualmente il repository fa sì che dnf upgrade gestisca Docker come qualsiasi altro pacchetto presente sul sistema.

Podman risponde già al comando docker?

Rocky Linux e AlmaLinux includono podman nei repository predefiniti e molte immagini VPS lo installano automaticamente. Alcune immagini fanno un passo ulteriore e installano podman-docker, che crea uno script shell in /usr/bin/docker e usa podman. Ogni comando docker immesso viene quindi eseguito da podman, perciò una guida scritta per Docker produce un output inatteso.

Il primo indizio è un banner. Lo script /usr/bin/docker verifica la presenza del file /etc/containers/nodocker e, se il file non esiste, stampa una riga prima di eseguire qualsiasi operazione:

Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.

Qualcuno potrebbe aver creato quel file per nascondere il banner, quindi non basarti soltanto su questo controllo. Chiedi al database dei pacchetti quale pacchetto possiede il binario:

command -v docker
rpm -qf "$(command -v docker)"

Una risposta che inizia con podman-docker indica che sta rispondendo podman. Una risposta che inizia con docker-ce-cli indica che si tratta del vero Docker. Se rpm -qf segnala che nessun pacchetto possiede il file, qualcuno lo ha installato manualmente: leggi lo script prima di considerarlo attendibile.

Podman usa le stesse immagini OCI ed è una scelta ragionevole. Se vuoi usare podman, puoi fermarti qui. Se vuoi Docker Engine, rimuovi prima i pacchetti in conflitto. Questo è l'elenco documentato da Docker per RHEL:

sudo dnf remove docker docker-client docker-client-latest docker-common \
  docker-latest docker-latest-logrotate docker-logrotate docker-engine podman runc

Prima di confermare, controlla cosa dnf prevede di rimuovere insieme a quei pacchetti. In un'immagine VPS appena creata, l'elenco è breve. Su un server già utilizzato, la rimozione di podman può rimuovere anche cockpit-podman o un altro strumento che dipende da esso.

In linea di principio è possibile mantenere podman insieme a Docker: rimuovi soltanto podman-docker, in modo da liberare il nome docker, e runc, che il pacchetto containerd.io sostituisce. La documentazione di Docker considera podman un pacchetto in conflitto, quindi Docker non supporta questa configurazione. Se l'installazione segnala ancora un conflitto, usa l'elenco completo di rimozione riportato sopra.

Aggiungere il repository di Docker con dnf config-manager

Docker pubblica pacchetti RPM per Enterprise Linux all’indirizzo download.docker.com. Il file del repository punta all’albero CentOS, che è quello utilizzato da Rocky Linux e AlmaLinux. Verificato ad agosto 2026, Docker documenta questo repository per CentOS Stream 9 e CentOS Stream 10.

sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

La versione 5 di dnf ha rimosso l’argomento --add-repo, quindi il secondo comando non funziona nelle versioni più recenti. Verifica quale versione è installata, quindi usa la sintassi corrispondente:

dnf --version

Se viene visualizzata una versione 5.x, usa invece la forma con il sottocomando:

sudo dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/centos/docker-ce.repo

Entrambe le forme scrivono lo stesso file in /etc/yum.repos.d/docker-ce.repo. La forma errata restituisce un errore relativo a un argomento sconosciuto, invece di produrre un risultato errato senza segnalarlo. Non rischi quindi di non accorgertene.

Il file del repository imposta baseurl su un percorso che contiene $releasever. dnf espande questa variabile in base al pacchetto che fornisce le informazioni sulla release. Rocky Linux e AlmaLinux la impostano sul numero della versione principale: 9 su EL 9 e 10 su EL 10. Per questo un repository CentOS viene risolto correttamente su un sistema Rocky. Verifica l’espansione prima dell’installazione:

sudo dnf repoinfo docker-ce-stable

Leggi la riga Repo-baseurl. Dovrebbe terminare con /9/x86_64/stable o /10/x86_64/stable. Se la release imposta $releasever su una versione puntuale come 9.6, durante il recupero dei metadati dnf segnala Status code: 404 per quell’URL. Correggi il problema modificando /etc/yum.repos.d/docker-ce.repo e sostituendo $releasever con il solo numero della versione principale.

Installare il motore e il plugin compose

sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Cinque pacchetti, ciascuno con una funzione precisa. docker-ce è il demone, dockerd. docker-ce-cli è il comando docker che si esegue. containerd.io è il runtime dei container gestito dal demone. docker-buildx-plugin crea le immagini. docker-compose-plugin fornisce docker compose come sottocomando.

Questi pacchetti non installano un binario docker-compose con il trattino. Si trattava di Compose v1, che ha raggiunto il fine vita a luglio 2023. Tutto ciò che richiama docker-compose con un trattino deve essere aggiornato a docker compose con uno spazio.

La prima installazione si interrompe per importare la chiave di firma di Docker e ne mostra l'impronta digitale. La chiave proviene da gpgkey=https://download.docker.com/linux/centos/gpg nel file del repository appena aggiunto. Prima di accettarla, confronta quindi l'impronta digitale visualizzata da dnf con quella dell'URL.

Un errore ricorre con sufficiente frequenza da meritare una menzione. Se dnf segnala che containerd.io richiede container-selinux e che nessun pacchetto lo fornisce, il repository AppStream è disabilitato. Esegui dnf repolist e verifica che appstream sia elencato, perché è da lì che container-selinux viene distribuito su EL 9 e EL 10.

Avvia Docker e verifica che sia in esecuzione

sudo systemctl enable --now docker
sudo systemctl status docker --no-pager
sudo docker run --rm hello-world

I pacchetti RPM di Docker lasciano il demone arrestato e disabilitato dopo l'installazione. Per questo questo passaggio compare nella pagina Docker per CentOS, ma non in quella per Ubuntu, dove il pacchetto deb avvia automaticamente il servizio. Se salti enable, Docker resta in esecuzione fino al riavvio successivo, poi rimane arrestato e arresta anche tutti i container.

systemctl status dovrebbe visualizzare Active: active (running). Il container hello-world dovrebbe stampare This message shows that your installation appears to be working correctly. e terminare. Se invece stampa un errore di autorizzazione su /var/run/docker.sock, hai omesso sudo. La sezione seguente sul gruppo docker risolve il problema.

Verifica separatamente il plugin Compose, perché è un pacchetto distinto e può mancare anche quando il motore funziona:

docker compose version

Un risultato corretto è simile a Docker Compose version v2.x.x. Il ripristino dei servizi dopo un riavvio è una questione distinta dall'abilitazione del demone: le policy di riavvio stabiliscono se i servizi Compose tornano in esecuzione all'avvio.

Perché un bind mount restituisce «permission denied»?

Rocky Linux e AlmaLinux eseguono SELinux (Security-Enhanced Linux) in modalità enforcing per impostazione predefinita. Verificalo con getenforce, che stampa Enforcing.

I container Docker vengono eseguiti con il tipo SELinux container_t, che può leggere e scrivere soltanto file con l'etichetta container_file_t. Una directory creata sull'host eredita l'etichetta assegnata dal percorso padre, che non è container_file_t. Il container riceve un rifiuto anche se, dal lato dell'host, proprietario, gruppo e permessi sembrano corretti. Puoi riprodurre il problema con tre comandi:

sudo mkdir -p /srv/site
echo hello | sudo tee /srv/site/index.html
sudo docker run --rm -v /srv/site:/usr/share/nginx/html:ro nginx:alpine cat /usr/share/nginx/html/index.html

Il container stampa:

cat: can't open '/usr/share/nginx/html/index.html': Permission denied

Due comandi mostrano la causa. ls -ldZ /srv/site stampa l'etichetta, che per un percorso sotto /srv è system_u:object_r:var_t:s0, non container_file_t. Poi sudo ausearch -m avc -ts recent stampa il record di audit del kernel, che contiene avc: denied { read }, un campo scontext= che identifica container_t e un campo tcontext= che identifica l'etichetta appena vista sulla directory. La mancata corrispondenza tra questi due campi spiega interamente il problema.

La correzione consiste nell'aggiungere un suffisso all'argomento del volume. Docker rietichetta il percorso:

sudo docker run --rm -v /srv/site:/usr/share/nginx/html:ro,z nginx:alpine cat /usr/share/nginx/html/index.html

:z in lettere minuscole rietichetta il contenuto come condiviso, permettendo a più container di usare la stessa directory. :Z in lettere maiuscole lo rietichetta come privato e non condiviso, associato a un solo container; un secondo container che legge lo stesso percorso riceve quindi un rifiuto. Usa :z per qualsiasi directory utilizzata anche da un sidecar o da un container di backup. Usa :Z per una directory di database di proprietà di un solo container.

La documentazione di Docker contiene un avviso che vale la pena ripetere, perché la rietichettatura è ricorsiva. Il bind mount di una directory di sistema come /home o /usr con :Z "rende il computer host inutilizzabile e potrebbe essere necessario rietichettare manualmente i file del computer host". Applica questi suffissi alle directory create per il container, mai a un percorso di sistema.

In Compose il suffisso va aggiunto alla stessa stringa:

services:
  web:
    image: nginx:alpine
    volumes:
      - /srv/site:/usr/share/nginx/html:ro,z

È facile incorrere in due limitazioni. Il flag --mount non può impostare un'etichetta SELinux, quindi usa -v quando ne hai bisogno. I volumi denominati non richiedono alcun suffisso, perché Docker etichetta autonomamente le directory che crea sotto /var/lib/docker/volumes.

Non disattivare SELinux. Usa sudo setenforce 0 soltanto per un test di un minuto: se il container funziona, il problema riguarda un'etichetta e la soluzione è :z. Riattivalo subito con sudo setenforce 1. In Enterprise Linux, permission denied su un bind mount può avere due cause distinte che dall'interno del container appaiono identiche. Una è l'etichetta SELinux. L'altra è la normale proprietà numerica di utente e gruppo, che è ciò che le variabili PUID e PGID servono a gestire. ls -lnZ mostra in una sola riga i permessi, il proprietario numerico e l'etichetta, permettendoti di capire quale dei due problemi stai affrontando.

Perché una porta pubblicata è raggiungibile quando firewalld sembra chiuso?

Firewalld è il firewall predefinito di Rocky Linux e AlmaLinux. Verificare che sia in esecuzione con sudo systemctl is-active firewalld. Ora pubblicare una porta e controllare quali porte firewalld considera aperte:

sudo docker run -d --name web -p 8080:80 nginx:alpine
sudo firewall-cmd --list-ports

firewall-cmd restituisce una riga vuota. Da un'altra macchina, curl -I http://YOUR_SERVER_IP:8080/ restituisce HTTP/1.1 200 OK. La porta è aperta a Internet, ma il firewall non la segnala.

La causa è il percorso seguito dal pacchetto. Le regole delle zone di firewalld filtrano il traffico destinato direttamente all'host. Una porta pubblicata non è destinata all'host: Docker installa una regola di NAT di destinazione (network address translation) che riscrive la destinazione con l'indirizzo del container prima che il pacchetto raggiunga il percorso di input dell'host. Il kernel inoltra quindi il pacchetto invece di consegnarlo localmente. Docker inserisce poi le proprie interfacce bridge in una zona di firewalld chiamata docker, il cui target è ACCEPT, e aggiunge una policy di forwarding chiamata docker-forwarding, che consente l'inoltro da qualsiasi zona alla zona docker. Le regole della zona non vedono mai il pacchetto.

La soluzione più pulita non richiede alcuna regola del firewall. Associare il lato host della pubblicazione all'interfaccia loopback e inserire un reverse proxy davanti al servizio:

sudo docker rm -f web
sudo docker run -d --name web -p 127.0.0.1:8080:80 nginx:alpine
curl -I http://127.0.0.1:8080/

Il comando locale curl restituisce HTTP/1.1 200 OK e la stessa richiesta da un'altra macchina non stabilisce più la connessione. Se nell'argomento -p non viene specificato alcun indirizzo dell'host, la porta viene pubblicata su tutte le interfacce. Considerare quindi un semplice -p 8080:80 come una scelta esplicita di esporre pubblicamente il servizio.

Quando un servizio deve essere raggiungibile da alcuni indirizzi ma non da altri, Docker riserva una chain dedicata. DOCKER-USER viene elaborata prima delle regole di accettazione proprie di Docker. Una regola inserita in questa chain resta quindi disponibile anche dopo il riavvio di Docker e la riscrittura delle sue chain:

sudo iptables -I DOCKER-USER -i enp1s0 ! -s 203.0.113.10 -j DROP
sudo iptables -S DOCKER-USER

Ricavare il nome dell'interfaccia da ip route show default invece di presumere eth0, perché le immagini EL attuali usano nomi come enp1s0 o ens3. In Rocky e AlmaLinux, il comando iptables è un livello di compatibilità sopra nftables e rende visibili le chain di Docker. Le regole aggiunte in questo modo scompaiono dopo un riavvio se non vengono salvate. Inserirle quindi in una unità systemd dopo averle verificate.

Docker Engine 28.0, rilasciato nel 2025, ha risolto un problema correlato: l'accesso instradato direttamente alle porte dei container mai pubblicate è ora bloccato nella chain DOCKER. Questa modifica non interessa le porte pubblicate, quindi tutto quanto descritto sopra vale ancora nelle versioni attuali. È utile adottare una pratica operativa: dopo ogni sudo firewall-cmd --reload, verificare nuovamente una porta pubblicata. Se non risponde più, sudo systemctl restart docker reinstalla le regole di Docker.

Gli amministratori Ubuntu incontrano lo stesso problema tramite uno strumento diverso, come spiegato in perché le porte Docker pubblicate ignorano le regole ufw. In entrambi i casi la causa è il percorso NAT. Cambia soltanto il firewall che lo precede.

Aggiungere un utente non root al gruppo docker

Digitare sudo prima di ogni comando docker diventa presto scomodo. Il gruppo docker evita questo passaggio:

sudo usermod -aG docker $USER
newgrp docker
docker run --rm hello-world

usermod -aG modifica /etc/group, ma la shell corrente ha già il proprio elenco di gruppi. La modifica non viene applicata finché non si avvia una nuova sessione. newgrp docker avvia una shell con il nuovo gruppo associato, così è possibile verificare subito la configurazione. Le nuove sessioni SSH lo acquisiscono automaticamente.

È importante comprendere quali privilegi concede questo gruppo. L'appartenenza al gruppo fornisce accesso in scrittura a /var/run/docker.sock. Qualsiasi processo in grado di comunicare con questo socket può chiedere al demone di avviare un container che monta il filesystem dell'host. Un comando mostra le conseguenze:

docker run --rm -v /:/host alpine wc -l /host/etc/shadow

Questo comando legge un file accessibile soltanto a root, usando un account che non dispone di privilegi sudo. Anche la documentazione post-installazione di Docker lo specifica: il gruppo docker concede privilegi equivalenti a quelli di root. Aggiungere un account a questo gruppo solo se si concederebbe allo stesso account anche sudo. Se si stanno configurando account su un nuovo server, valutare questa scelta insieme al resto della configurazione di un utente con privilegi minimi su un VPS, non in un secondo momento.

Docker offre anche una modalità rootless, che esegue il demone con un utente senza privilegi. Si tratta di un percorso di installazione separato. Cambia inoltre il comportamento dei driver di storage e delle porte inferiori a 1024. Va quindi pianificata come un progetto autonomo, non come un flag da aggiungere in seguito.

Dove proseguire

Ora disponi del motore, del plugin Compose, di un servizio che sopravvive al riavvio e dei tre comportamenti specifici di EL documentati sopra. Il passaggio successivo consiste nell'impostare un compose.yaml per ogni servizio; l'anatomia di un file Compose descrive il formato del file e i comandi che lo gestiscono. Se questo è il tuo primo host container, eseguire Docker su un VPS tratta gli aspetti relativi a dimensionamento, storage e gestione delle immagini che questa guida non approfondisce.

FAQ

Il repository Docker per CentOS funziona su Rocky Linux e AlmaLinux?

Sì. Aggiungi https://download.docker.com/linux/centos/docker-ce.repo con dnf config-manager. Il baseurl in quel file contiene $releasever e Rocky Linux e AlmaLinux lo espandono nel numero della versione principale. Di conseguenza, un sistema EL 9 usa l'albero CentOS 9 e un sistema EL 10 usa l'albero CentOS 10. Verifica l'espansione con sudo dnf repoinfo docker-ce-stable e leggi la riga Repo-baseurl. Un Status code: 404 durante il recupero dei metadati da parte di dnf indica che la variabile è stata espansa in una release correttiva. Modifica /etc/yum.repos.d/docker-ce.repo per usare il solo numero della versione principale.

Docker e podman possono essere installati sullo stesso server?

La documentazione di Docker elenca podman e runc come pacchetti in conflitto e indica di rimuoverli entrambi prima di installare Docker Engine. Il conflitto concreto riguarda il pacchetto podman-docker, che possiede /usr/bin/docker e trasforma ogni comando docker in un comando podman. Esegui rpm -qf "$(command -v docker)" per verificare quale pacchetto possiede quel percorso. Se l'output inizia con podman-docker, sta rispondendo podman. Mantenere entrambi i motori non è una configurazione supportata da Docker. Su un server, quindi, scegli uno dei due.

Perché il mio container restituisce un errore di autorizzazione su un bind mount?

Su Rocky Linux e AlmaLinux SELinux è abilitato in modalità enforcing per impostazione predefinita. I container vengono eseguiti con il tipo container_t e possono accedere soltanto ai file con l'etichetta container_file_t. Di conseguenza, una directory appena creata ha l'etichetta errata e l'accesso viene negato indipendentemente dal proprietario e dai permessi. Verifica la situazione con ls -ldZ sul percorso dell'host e con sudo ausearch -m avc -ts recent, che stampa avc: denied insieme ai due contesti non corrispondenti. Aggiungi :z all'argomento del volume per i contenuti condivisi tra container oppure :Z per i contenuti privati di un solo container. Non indicare mai :Z per /home o /usr, perché la rietichettatura è ricorsiva e danneggerebbe il sistema host.

Devo aprire una porta in firewalld per pubblicare la porta di un container?

No, ed è proprio questo il problema. La regola NAT di Docker riscrive l'indirizzo di destinazione prima che il pacchetto raggiunga il percorso di input dell'host. Le regole delle zone di firewalld, quindi, non lo esaminano. Docker inserisce inoltre i propri bridge in una zona firewalld chiamata docker con destinazione ACCEPT. Un container avviato con -p 8080:80 è raggiungibile da Internet, mentre sudo firewall-cmd --list-ports non restituisce alcun output. Pubblica il servizio su un indirizzo specifico con -p 127.0.0.1:8080:80 quando deve essere raggiungibile soltanto dall'host, oppure inserisci le regole di filtraggio nella catena DOCKER-USER, che Docker elabora prima delle proprie regole di accettazione.

Aggiungere il mio utente al gruppo docker è sicuro?

Concede i privilegi di root. Un membro del gruppo docker può scrivere in /var/run/docker.sock e docker run --rm -v /:/host alpine wc -l /host/etc/shadow può quindi leggere un file accessibile solo a root usando un account privo di privilegi sudo. La documentazione post-installazione di Docker dichiara la stessa equivalenza. Aggiungi soltanto account a cui concederesti già sudo e continua a usare sudo docker per gli account condivisi o di servizio. La modalità rootless è l'alternativa quando servono container eseguiti da un utente non privilegiato. Richiede però un percorso di installazione separato, non una semplice impostazione.