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.

Immagine generata con AI
Immagine generata con AI
Riassunto generato automaticamente tramite intelligenza artificiale:

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 partenza
  • WORKDIR, directory di lavoro dentro il container
  • COPY / ADD, copia file dall'host all'immagine
  • RUN, esegue comandi in fase di build (installazioni, download)
  • ENV, imposta variabili d'ambiente
  • EXPOSE, documenta quali porte usa il container (non le pubblica da sola, serve comunque -p al run)
  • CMD, comando eseguito all'avvio del container (uno solo, sovrascrivibile)
  • ENTRYPOINT, simile a CMD ma 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.

.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:

  1. Docker Desktop configurato con backend WSL2 (non Hyper-V puro)
  2. Driver NVIDIA aggiornati lato Windows (non dentro la distro WSL)
  3. 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 --rm di 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 RUN se servono solo per test occasionali: preferisci scaricarle a runtime in un container effimero
  • Controlla periodicamente docker system df per 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 run diventano 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, .git e file sensibili tramite .dockerignore per 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 2 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 --set-sparse true 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.

Articolo scritto con il supporto di LLM per la formattazione e la struttura del codice.

Commenti