È uscito il Corso Java Completo
Torna al blog

Comandi Docker essenziali: la guida pratica

I comandi Docker essenziali spiegati per compito: avviare container, leggere i log, entrare dentro per il debug, costruire immagini e liberare spazio su disco.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

9 min di lettura

Docker lo hai installato, il primo container lo hai avviato copiando un comando da una guida. Poi il container si ferma e non sai perché, il disco si riempie senza spiegazione e non ricordi come si entrava dentro per guardare. In questo articolo trovi i comandi che servono davvero, organizzati per compito e non in ordine alfabetico, con le opzioni che cambiano il risultato e gli errori che fanno perdere il pomeriggio.

Cos'è la riga di comando di Docker

Il comando docker è l'unico modo con cui parli al demone Docker: tutto quello che fai — avviare container, costruire immagini, creare reti e volumi — passa da lì, e l'interfaccia grafica di Docker Desktop non fa nulla di diverso.

Se non sai ancora cosa sia un container, parti da cos'è Docker e da cos'è un container. Qui diamo per scontato che tu ne abbia già avviato almeno uno.

Un consiglio prima di iniziare: quasi ogni comando accetta --help. docker run --help è più veloce di una ricerca su internet e non ti mostra la versione sbagliata.

Avviare un container: docker run

È il comando che userai di più, ed è anche quello con più opzioni. Il caso realistico:

docker run -d \
  --name mio-nginx \
  -p 8080:80 \
  -v /srv/sito:/usr/share/nginx/html:ro \
  --restart unless-stopped \
  nginx:1.27-alpine

Cosa fa ogni pezzo:

  • -d lo avvia in background (detached). Senza, il terminale resta agganciato e con Ctrl+C fermi il container.
  • --name gli dà un nome. Senza, Docker ne inventa uno tipo nervous_kepler e ogni comando successivo diventa un copia-incolla di ID.
  • -p 8080:80 mappa la porta: a sinistra la porta dell'host, a destra quella dentro il container. Invertirle è l'errore numero uno.
  • -v monta una cartella dell'host dentro il container. Il :ro finale la rende in sola lettura.
  • --restart unless-stopped lo fa ripartire dopo un riavvio della macchina. Su un VPS è quasi sempre quello che vuoi.
  • nginx:1.27-alpine è l'immagine. Metti sempre un tag esplicito: latest oggi e latest fra tre mesi sono due software diversi.

Altre due opzioni che ricorrono spesso:

docker run --rm -it ubuntu:24.04 bash    # container usa e getta
docker run -e DATABASE_URL=postgres://... -d mia-app

--rm cancella il container quando esce: perfetto per prove al volo, perché altrimenti ti restano decine di container spenti in giro. -it ti dà un terminale interattivo. -e passa una variabile d'ambiente.

Vedere cosa sta girando: ps

docker ps           # solo i container in esecuzione
docker ps -a        # anche quelli fermi
docker ps -a --filter "status=exited"
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

docker ps -a è il primo comando da lanciare quando "il container non c'è più". Nove volte su dieci c'è ancora, ma è uscito con un codice di errore, e la colonna STATUS te lo dice: Exited (1) 3 minutes ago.

Capire perché non funziona: logs

docker logs mio-nginx
docker logs -f --tail 100 mio-nginx      # segui in tempo reale
docker logs --since 10m mio-nginx        # solo gli ultimi 10 minuti
docker logs -t mio-nginx                 # con marca temporale

-f (follow) è l'equivalente di tail -f, ed è quello che tieni aperto in un terminale mentre provi l'applicazione nell'altro. Se non conosci tail e gli altri strumenti di base, li trovi in comandi Linux essenziali.

Attenzione a una cosa: Docker cattura solo quello che l'applicazione scrive su stdout e stderr. Se la tua app scrive i log dentro un file nel container, docker logs non mostrerà niente e penserai che sia rotta. In un container i log vanno sull'output standard, punto.

Entrare dentro un container: exec

Quando i log non bastano, apri una shell dentro il container in esecuzione:

docker exec -it mio-nginx sh
docker exec -it mio-postgres psql -U postgres
docker exec mio-nginx cat /etc/nginx/nginx.conf

sh e non bash: le immagini basate su Alpine non hanno bash, e ti prendi un executable file not found che sembra un errore grave e invece è solo la shell sbagliata. Prova sh prima.

Due cose da sapere per non arrabbiarti:

  • exec funziona solo su container in esecuzione. Se il container esce subito dopo l'avvio non riesci a entrarci. In quel caso avvia una copia dell'immagine sovrascrivendo il comando: docker run --rm -it mia-app sh, così parte la shell invece dell'applicazione e puoi guardarti intorno.
  • Le modifiche che fai dentro spariscono. Se installi un pacchetto con apt install dentro il container, al prossimo docker run non c'è più. Va messo nel Dockerfile.

C'è anche docker attach, che ti aggancia al processo principale: evitalo, perché con Ctrl+C rischi di fermare il container. exec è quasi sempre la risposta giusta.

Costruire immagini: build

docker build -t mia-app:1.0 .
docker build -t mia-app:1.0 -f Dockerfile.prod --no-cache .

Il punto finale è il contesto di build: la cartella che Docker impacchetta e manda al demone. Se lanci docker build in una cartella che contiene node_modules o un .git da mezzo giga, la build diventa lentissima ancora prima di iniziare. Un .dockerignore risolve, e quasi nessuno lo scrive alla prima:

node_modules
.git
*.log
.env

--no-cache ricostruisce tutto ignorando la cache: serve quando sospetti che uno strato vecchio ti stia dando risultati stantii, ma è lento, quindi non usarlo per abitudine.

Gestire immagini e container

docker images                       # elenco immagini
docker pull redis:7                 # scarica senza avviare
docker stop mio-nginx               # SIGTERM, poi SIGKILL dopo 10s
docker stop -t 30 mio-db            # aspetta 30 secondi
docker start mio-nginx
docker restart mio-nginx
docker rm mio-nginx                 # elimina il container fermo
docker rmi nginx:1.27-alpine        # elimina l'immagine
docker inspect mio-nginx            # tutti i dettagli, in JSON
docker stats                        # CPU e memoria in tempo reale

docker stop non è docker kill. Il primo manda un segnale di chiusura ordinata e lascia tempo all'applicazione di chiudere connessioni e scrivere su disco; il secondo stacca la spina. Su un database, docker kill è un modo eccellente per corrompere i dati.

Volumi: i dati che devono sopravvivere

Il filesystem di un container è temporaneo. Cancelli il container, i dati se ne vanno. È la cosa che sorprende chi arriva dalle macchine virtuali, dove il disco resta.

docker volume create dati-postgres
docker volume ls
docker volume inspect dati-postgres
docker run -d -v dati-postgres:/var/lib/postgresql/data postgres:16

Due modi di montare, e vanno distinti:

TipoSintassiQuando usarlo
Volume gestito-v nome:/percorsoDati di produzione: database, caricamenti utente
Bind mount-v /host/cartella:/percorsoSviluppo: il codice sul tuo computer visto dal container
tmpfs--tmpfs /percorsoFile temporanei che devono stare solo in RAM

Con i bind mount su Linux ti scontrerai con i permessi: il processo dentro il container ha un suo UID e potrebbe non poter scrivere nella cartella dell'host. L'argomento è spiegato in permessi dei file e chmod.

Reti: far parlare i container tra loro

docker network create rete-app
docker run -d --name db --network rete-app postgres:16
docker run -d --name api --network rete-app -p 3000:3000 mia-api

Su una rete creata da te, i container si raggiungono per nome. Dentro api, l'indirizzo del database è db:5432. Non serve conoscere gli IP e non serve pubblicare la porta del database sull'host — anzi, non pubblicarla è più sicuro.

Sulla rete bridge predefinita questa risoluzione dei nomi non funziona, ed è la ragione per cui a volte "i container non si vedono". Nella pratica quasi tutti smettono di creare reti a mano e passano a Docker Compose, che crea la rete da solo.

Il disco che si riempie: prune

Questa è la sezione che quasi nessuno legge finché il server non risponde no space left on device. Docker non cancella niente da solo: immagini di build vecchie, container usciti, volumi orfani e cache di build restano lì per sempre.

Prima guarda quanto spazio occupa:

docker system df
docker system df -v      # dettaglio per immagine e volume

Poi pulisci, dal meno al più aggressivo:

docker container prune              # container fermi
docker image prune                  # immagini senza tag ("dangling")
docker image prune -a               # tutte quelle non usate da un container
docker builder prune                # cache di build (spesso è la più grossa)
docker system prune                 # container + reti + immagini dangling + cache
docker system prune -a --volumes    # tutto, volumi compresi

--volumes è la riga da leggere due volte. Cancella i volumi non collegati a nessun container, e "nessun container" include il volume del database di cui hai appena rimosso il container per ricrearlo. Non c'è cestino: i dati sono persi.

Regola pratica: docker system prune senza opzioni lo puoi lanciare tranquillamente. Con -a --volumes, solo dopo aver controllato docker volume ls e sapendo cosa c'è dentro. Su un server con CI/CD che costruisce immagini a ogni push, una pulizia programmata evita di svegliarsi con il disco pieno.

Tre errori che fanno perdere ore

Le porte invertite in -p. -p 80:8080 quando volevi -p 8080:80. Il container parte, docker ps è verde, il browser non risponde e cerchi il problema nell'applicazione.

Modificare un file dentro il container e credere di aver risolto. Funziona finché il container vive. Al primo riavvio o alla prima ricostruzione torna tutto come prima. Se una modifica deve restare, va nel Dockerfile o in un volume.

Usare latest ovunque. Un docker pull sei mesi dopo ti porta una versione maggiore diversa, la build che funzionava si rompe e non hai modo di tornare indietro perché non sai quale versione avevi. Fissa i tag.

In sintesi

I comandi che coprono il 90% delle giornate sono cinque: run per avviare, ps -a per vedere cosa c'è, logs -f per capire cosa succede, exec -it per entrare a guardare e system df per sapere quanto spazio stai occupando.

Il container è usa e getta, i dati no: tutto quello che deve sopravvivere sta in un volume, tutto il resto si ricostruisce dall'immagine.

La pulizia va fatta prima che serva. docker system df una volta al mese e docker system prune quando cresce troppo — con --volumes solo se sai esattamente cosa stai cancellando.

Quando i container diventano più di uno e devono parlarsi, la riga di comando smette di bastare e si passa a Docker Compose. Per il contesto più ampio, vedi cos'è Docker e virtualizzazione contro container.