Guida completa a Docker
Panoramica completa sull'architettura e sul funzionamento dei container Docker. La guida copre i comandi essenziali per la gestione di immagini e volumi, la scrittura di Dockerfile multi-stage, l'uso di Docker Compose e la configurazione dell'ambiente su Windows tramite WSL2.
Docker è una piattaforma di containerizzazione che isola le applicazioni con le loro dipendenze in ambienti leggeri e portatili, condividendo il kernel dell'host per un'efficienza superiore alle VM. I concetti chiave includono immagini immutabili che definiscono l'applicazione, container come istanze in esecuzione, registry per l'archiviazione e Dockerfile per la costruzione delle immagini.
Cos'è docker
Docker è una piattaforma di containerizzazione, permette di eseguire applicazioni in ambienti isolati (container) che includono tutto il necessario per funzionare: codice, runtime, librerie, dipendenze di sistema, senza dover installare nulla direttamente sulla macchina host.
La differenza principale rispetto a una macchina virtuale è che i container condividono il kernel del sistema operativo host, risultando molto più leggeri e veloci da avviare.
Concetti fondamentali
Immagine (image) Un pacchetto immutabile che contiene filesystem, dipendenze e istruzioni per eseguire un'applicazione. Le immagini sono composte da layer sovrapposti, ognuno corrispondente a un'istruzione del Dockerfile che l'ha generata.
Container Un'istanza in esecuzione di un'immagine. È possibile avviare più container dalla stessa immagine, ognuno isolato dagli altri.
Registry Un archivio di immagini scaricabili. Docker Hub è il registry pubblico predefinito (es. node:22, postgres:16, yanwk/comfyui-boot).
Dockerfile Un file di testo con le istruzioni per costruire un'immagine personalizzata.
Volume Uno spazio di archiviazione gestito da Docker, indipendente dal ciclo di vita del container, usato per persistere dati.
Comandi essenziali
Gestione container
docker run <opzioni> <immagine>
docker ps
docker ps -a
docker stop <nome-o-id>
docker rm <nome-o-id>
docker exec -it <nome-o-id> bash
docker logs <nome-o-id>
docker logs -f <nome-o-id>
In ordine, i comandi sopra: avviano un container da un'immagine (scaricandola se non presente), elencano i container in esecuzione, elencano tutti i container anche quelli fermi, fermano un container, rimuovono un container fermo, eseguono un comando dentro un container già attivo, mostrano i log e infine li mostrano in tempo reale.
Opzioni più usate con docker run
| Opzione | Effetto |
|---|---|
| --rm | Rimuove il container automaticamente allo stop |
| -it | Modalità interattiva con terminale allegato |
| -d | Modalità detached, esegue in background |
| --name | Assegna un nome al container |
| -p host:container | Mappa una porta dell'host su una del container |
| -v | Monta un volume o bind mount |
| -e | Imposta una variabile d'ambiente |
| --gpus all | Espone le GPU al container (richiede NVIDIA Container Toolkit) |
| -w | Imposta la working directory dentro il container |
Gestione immagini
docker pull <immagine>
docker images
docker rmi <immagine>
docker build -t <nome-immagine> .
Questi comandi scaricano un'immagine senza avviarla, elencano le immagini locali, rimuovono un'immagine e costruiscono un'immagine da un Dockerfile nella directory corrente.
Persistenza dei dati, bind mount vs volume
Per default un container è effimero: tutto ciò che si scrive al suo interno sparisce quando viene rimosso. Esistono due modi per persistere i dati.
Bind mount, collega una cartella reale dell'host al container:
docker run -v "C:\percorso\cartella:/app" <immagine>
I file sono visibili e gestibili direttamente da Windows. Utile quando si vuole editare il codice con il proprio editor mentre gira nel container.
Volume Docker, spazio gestito internamente da Docker, non visibile come cartella Windows:
docker volume create nome-volume
docker run -v nome-volume:/app <immagine>
Il volume sopravvive alla rimozione del container ma resta isolato, facile da eliminare del tutto:
docker volume rm nome-volume
docker volume ls
Combinazione tipica (codice sincronizzato, dipendenze isolate):
docker run -v "${PWD}:/app" -v node-modules-cache:/app/node_modules -w /app node:22 npm install
Questo pattern evita che node_modules venga scritto sul disco Windows, dove su WSL2 l'I/O tra molti file piccoli è più lento rispetto al filesystem interno di Docker.
Rete e porte
-p 4200:4200
-p 8080:4200
La prima riga mappa la porta 4200 del container sulla 4200 dell'host, la seconda mappa su una porta host diversa (8080 verso 4200).
Molti dev server (Angular, Vite, ecc.) per default ascoltano solo su localhost dentro il container. Serve avviarli con un'opzione che li faccia ascoltare su tutte le interfacce, altrimenti non sono raggiungibili dall'host anche con la porta mappata correttamente (es. ng serve --host 0.0.0.0).
Reti Docker personalizzate
Oltre al mapping di porte verso l'host, Docker permette di creare reti bridge dedicate per far comunicare più container tra loro tramite hostname, senza esporre porte all'esterno:
docker network create --driver bridge rete-progetto
docker run -d --network rete-progetto --name db postgres:16
docker run -d --network rete-progetto --name backend mio-progetto
Su una rete personalizzata, il container backend può raggiungere il database semplicemente usando db come hostname, invece di dover conoscere il suo indirizzo IP. Docker Compose crea automaticamente una rete di questo tipo per ogni progetto, motivo per cui i servizi definiti in un docker-compose.yml si vedono tra loro per nome senza configurazione aggiuntiva.
Dockerfile
Un Dockerfile descrive come costruire un'immagine, istruzione per istruzione.
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 4200
CMD ["npm", "start", "--", "--host", "0.0.0.0"]
Istruzioni principali:
FROM, immagine base di partenzaWORKDIR, directory di lavoro dentro il containerCOPY/ADD, copia file dall'host all'immagineRUN, esegue comandi in fase di build (installazioni, download)ENV, imposta variabili d'ambienteEXPOSE, documenta quali porte usa il container (non le pubblica da sola, serve comunque-pal run)CMD, comando eseguito all'avvio del container (uno solo, sovrascrivibile)ENTRYPOINT, simile aCMDma meno facilmente sovrascrivibile, spesso usato insieme
Build e run:
docker build -t mio-progetto .
docker run --rm -it -p 4200:4200 mio-progetto
Ogni RUN che scarica file pesanti (es. un checkpoint AI) li incorpora permanentemente nei layer dell'immagine, aumentandone le dimensioni su disco in modo fisso, diverso dallo scaricarli a runtime in un container effimero, dove occupano spazio solo mentre il container esiste.
Multi-stage build
Per immagini di produzione, costruire e servire l'applicazione nello stesso stage porta a immagini gonfiate da strumenti di build non necessari a runtime (compilatori, dipendenze di sviluppo). Il pattern multi-stage separa la fase di build da quella di esecuzione, copiando nell'immagine finale solo gli artefatti compilati:
FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22 AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
EXPOSE 4200
CMD ["node", "dist/server.js"]
Lo stage build contiene tutte le dipendenze e i tool necessari alla compilazione, lo stage runtime eredita solo ciò che serve per far girare l'app, con un'immagine finale sensibilmente più piccola e con una superficie di attacco ridotta.
npm ci installa le dipendenze esattamente come bloccate nel file di lock, a differenza di npm install che può aggiornarle. È la scelta preferibile in fase di build per garantire riproducibilità. .dockerignore
Così come .gitignore esclude file dal controllo versione, un file .dockerignore nella directory del progetto esclude file dal contesto di build inviato al demone Docker:
node_modules
.git
dist
*.log
.env
Senza questo file, l'intera cartella node_modules locale (spesso enorme) viene inviata al demone a ogni build, rallentando il processo e rischiando di sovrascrivere dipendenze già installate correttamente dentro il container.
Esecuzione non privilegiata
Per default, i processi in un container Docker girano come utente root. In caso di vulnerabilità nell'applicazione, questo aumenta il potenziale impatto di un attacco. Le immagini ufficiali Node, tra cui node:22, includono già un utente non privilegiato predefinito che può essere attivato nel Dockerfile:
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
USER node
EXPOSE 4200
CMD ["npm", "start", "--", "--host", "0.0.0.0"]
Healthcheck
L'istruzione HEALTHCHECK permette a Docker di monitorare autonomamente lo stato di salute di un container, informazione visibile in docker ps e utilizzata da Docker Compose per gestire l'ordine di avvio dei servizi dipendenti:
HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD curl -f http://localhost:4200 || exit 1
Se il comando restituisce un codice diverso da zero per il numero di tentativi configurato, il container viene marcato come unhealthy.
Docker compose
Per progetti con più servizi collegati (es. backend Spring Boot + database Postgres), Docker Compose evita di scrivere comandi docker run lunghi e ripetuti.
docker-compose.yml:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: test
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
backend:
build: .
ports:
- "8080:8080"
depends_on:
- db
volumes:
pgdata:
Comandi:
docker compose up
docker compose up -d
docker compose down
docker compose down -v
In ordine: avviano tutti i servizi definiti, li avviano in background, fermano tutto, fermano e rimuovono anche i volumi nominati.
Gpu su windows (wsl2)
Per usare --gpus all servono tre prerequisiti:
- Docker Desktop configurato con backend WSL2 (non Hyper-V puro)
- Driver NVIDIA aggiornati lato Windows (non dentro la distro WSL)
- Integrazione WSL2 abilitata per la distro in uso nelle impostazioni di Docker Desktop
Se --gpus all fallisce con un errore relativo al driver, di solito la causa è WSL2 non aggiornato (wsl --update) o l'integrazione non abilitata per la distro corretta. La sezione 13 approfondisce i comandi WSL utili in questi casi.
Pulizia e gestione dello spazio
docker system df
docker system prune
docker system prune --volumes
docker image prune -a
In ordine: mostrano quanto spazio occupano immagini, container, volumi e cache di build, rimuovono container fermi/immagini non taggate/cache inutilizzata, fanno lo stesso rimuovendo anche i volumi non referenziati, e rimuovono tutte le immagini non usate da nessun container anche se taggate.
Su Windows, lo storage effettivo di Docker vive nel disco virtuale di WSL2 (file .vhdx). Anche dopo un prune, il file può restare fisicamente grande sul disco Windows finché non viene compattato:
wsl --shutdown
seguito da un'ottimizzazione del vhdx tramite diskpart o strumenti dedicati, se lo spazio recuperato non basta. La sezione 13 include anche un comando WSL nativo per ridurre dinamicamente questo spazio.
Sicurezza dell'immagine
Oltre alle buone pratiche di costruzione (multi-stage, utente non privilegiato, .dockerignore), è buona norma scansionare periodicamente le immagini alla ricerca di vulnerabilità note nelle dipendenze e nei pacchetti di sistema inclusi, prima di promuoverle in produzione:
trivy image <nome-immagine>
Casi pratici di riferimento
Ambiente ComfyUI completamente effimero (nessuna persistenza, download ogni volta):
docker run --rm -it --gpus all -p 8188:8188 --name comfyui-ephemeral yanwk/comfyui-boot:cu124-slim
Ambiente ComfyUI con modelli persistenti tra un test e l'altro nella stessa giornata:
docker volume create test-vol
docker run --rm -it --gpus all -p 8188:8188 -v test-vol:/root --name comfyui-ephemeral yanwk/comfyui-boot:cu124-slim
docker volume rm test-vol
Sviluppo Angular senza Node installato su Windows:
docker run --rm -it -v "${PWD}:/app" -v node-cache:/app/node_modules -w /app -p 4200:4200 node:22 npm start -- --host 0.0.0.0
Database Postgres per test locali di un backend Spring Boot:
docker volume create pgdata-progetto
docker run --rm -it --name pg-test -e POSTGRES_PASSWORD=test -p 5432:5432 -v pgdata-progetto:/var/lib/postgresql/data postgres:16
Buone pratiche
- Usa
--rmdi default per container di test, evita accumulo di container fermi dimenticati - Preferisci volumi nominati ai bind mount quando i dati non devono essere navigabili da Windows (più veloci su WSL2, più facili da eliminare in blocco)
- Assegna nomi univoci a volumi e container per progetto, per evitare collisioni tra progetti diversi
- Non installare dipendenze pesanti in
RUNse servono solo per test occasionali: preferisci scaricarle a runtime in un container effimero - Controlla periodicamente
docker system dfper capire dove va lo spazio, prima che il vhdx di WSL2 diventi ingombrante - Per progetti multi-servizio, passa a Docker Compose appena i comandi
docker rundiventano difficili da ricordare o coordinare a mano - Usa build multi-stage e un utente non privilegiato nelle immagini destinate alla produzione, non solo negli ambienti di test locali
- Escludi sempre
node_modules,.gite file sensibili tramite.dockerignoreper build più rapide e immagini più pulite
Comandi WSL utili per Docker Desktop su Windows
Docker Desktop su Windows si appoggia a WSL2 per eseguire i container. Conoscere i comandi WSL principali aiuta a diagnosticare e risolvere i problemi più comuni, oltre a tenere sotto controllo le risorse assegnate.
| Comando | Effetto |
|---|---|
| Gestione distribuzioni | |
| wsl -l -v | Lista delle distro installate, con versione WSL e stato |
| wsl -l --online | Lista delle distro disponibili da installare |
| wsl --install | Installa una distro (es. Ubuntu) |
| wsl --install --no-distribution | Installa solo la piattaforma WSL, senza distro |
| wsl --unregister | Disinstalla completamente una distro, cancellandone anche i dati |
| wsl --set-default | Imposta la distro di default |
| wsl --set-version | Converte una distro da WSL1 a WSL2 |
| Avvio e controllo | |
| wsl | Avvia la distro di default |
| wsl -d | Avvia una distro specifica |
| wsl --shutdown | Spegne tutte le istanze WSL, utile dopo problemi con Docker |
| wsl --terminate | Spegne una singola distro senza toccare le altre |
| wsl --status | Mostra la versione di default e lo stato generale |
| Aggiornamenti | |
| wsl --update | Aggiorna il kernel Linux di WSL |
| wsl --update --pre-release | Aggiorna alla versione pre-release, utile per bug fix recenti |
| wsl --version | Mostra le versioni di WSL, kernel e componenti correlati |
| Risorse e configurazione | |
| wsl --manage | Riduce dinamicamente lo spazio occupato dal vhdx |
| Diagnostica rapida per problemi Docker | |
| wsl -l -v | Verifica che docker-desktop e docker-desktop-data siano presenti e in stato Running |
| wsl --status | Controlla che la versione di default sia 2, non 1 |
Per limitare RAM e CPU assegnate a WSL2, utile se Docker consuma troppa memoria, si crea o modifica un file .wslconfig in C:\Users\<utente>\:
[wsl2]
memory=8GB
processors=4
Dopo aver modificato il file, è sempre necessario eseguire wsl --shutdown seguito da un riavvio della distro perché le modifiche abbiano effetto.