Docker guida completa
π³ Docker β Guida Completa
Docker Γ¨ la piattaforma di containerizzazione piΓΉ usata al mondo. Questa guida spiega cos'Γ¨, come funziona, come installarlo su Fedora/RHEL e Ubuntu, e include un laboratorio pratico per imparare facendo.
Indice
- #1. Cos'Γ¨ Docker e perchΓ© esiste
- #2. I concetti fondamentali
- #3. Architettura di Docker
- #4. Installazione su Fedora e RHEL
- #5. Installazione su Ubuntu e Debian
- #6. Configurazione post-installazione
- #7. I comandi essenziali
- #8. Dockerfile β costruire immagini custom
- #9. Docker Compose
- #10. Networking in Docker
- #11. Volumi e persistenza dei dati
- #12. π§ͺ Laboratorio pratico
1. Cos'Γ¨ Docker e perchΓ© esiste
Il problema che Docker risolve
Prima di Docker esisteva un problema cronico nello sviluppo software, riassumibile in una frase che ogni sviluppatore ha detto o sentito almeno una volta:
"Sul mio computer funziona."
Il codice gira perfettamente sulla macchina dello sviluppatore, ma si rompe in produzione. PerchΓ©? Le cause sono sempre le stesse: versioni diverse di librerie, variabili d'ambiente diverse, sistemi operativi diversi, dipendenze mancanti o in conflitto.
La soluzione: il container
Docker risolve questo problema con il concetto di container: un pacchetto autonomo e isolato che contiene l'applicazione insieme a tutto ciΓ² di cui ha bisogno per girare β librerie, runtime, configurazioni, file di sistema.
Un container avviato su un portatile con Ubuntu si comporta esattamente allo stesso modo su un server con CentOS, su un Mac, su Windows o su qualsiasi cloud provider. L'ambiente Γ¨ sempre identico.
Spesso Docker viene confuso con la virtualizzazione. Sono tecnologie diverse con scopi diversi.
| Macchina Virtuale | Container Docker | |
|---|---|---|
| Virtualizza | Hardware completo | Solo il sistema operativo |
| Include | Kernel + OS completo | Solo le librerie necessarie |
| Dimensione | Gigabyte | Megabyte |
| Avvio | Minuti | Secondi (o meno) |
| Isolamento | Totale | Parziale (condivide il kernel) |
| Overhead | Alto | Quasi zero |
I container condividono il kernel del sistema operativo host. Non emulano hardware: usano quello reale. Per questo sono leggerissimi e veloci.
Quando usare Docker
Docker Γ¨ ideale per:
- Sviluppo locale β avere l'ambiente identico tra tutti i membri del team
- Microservizi β ogni servizio nel suo container, indipendente dagli altri
- CI/CD β build riproducibili in pipeline automatizzate
- Deploy β portare in produzione un'immagine testata e immutabile
- Testing β isolare test in ambienti puliti e usa-e-getta
- Database e servizi β avviare MySQL, Redis, Postgres in secondi senza installarli
2. I concetti fondamentali
Capire questi quattro concetti Γ¨ capire Docker al 90%.
πΌοΈ Immagine (Image)
Un'immagine Γ¨ un modello di sola lettura da cui creare i container. Γ paragonabile a una fotografia o a un template: descrive il sistema operativo, le librerie installate, i file presenti e il comando da eseguire all'avvio.
Le immagini sono costruite a strati (layers). Ogni istruzione nel Dockerfile aggiunge un layer all'immagine. Questo sistema Γ¨ efficiente: se due immagini condividono gli stessi layer di base, quei layer vengono memorizzati una volta sola sul disco.
βββββββββββββββββββββββββββββββ
β Layer 4: copia il codice β β tuo Dockerfile
β Layer 3: installa Node.js β β tuo Dockerfile
β Layer 2: aggiorna i pacch. β β tuo Dockerfile
β Layer 1: ubuntu:22.04 β β immagine base
βββββββββββββββββββββββββββββββ
π¦ Container
Un container Γ¨ un'istanza in esecuzione di un'immagine. Dall'immagine si creano i container, esattamente come da un programma si creano i processi.
Puoi avviare decine di container dalla stessa immagine: saranno tutti isolati tra loro, avranno il proprio filesystem, la propria rete, i propri processi. Modificare un container non modifica l'immagine da cui Γ¨ stato creato.
Immagine nginx:latest
β
βββββββββββΌββββββββββ
βΌ βΌ βΌ
Container 1 Container 2 Container 3
(porta 80) (porta 81) (porta 82)
π Registry
Un registry Γ¨ un repository di immagini. Il registry ufficiale Γ¨ Docker Hub (hub.docker.com), dove trovi immagini ufficiali di praticamente qualsiasi software: nginx, postgres, redis, python, node, ubuntu, alpine e migliaia di altri.
# Le immagini hanno questo formato:
registry/utente/nome:tag
# Esempi:
ubuntu:22.04 # immagine ufficiale Ubuntu
nginx:latest # immagine ufficiale Nginx
postgres:16 # immagine ufficiale PostgreSQL
myuser/mia-app:v1.2.3 # immagine personale su Docker Hub
ghcr.io/org/repo:main # GitHub Container Registry
π Dockerfile
Un Dockerfile Γ¨ un file di testo con le istruzioni per costruire un'immagine. Γ la ricetta: parti da un'immagine base, aggiungi le tue personalizzazioni, definisci cosa deve fare il container all'avvio.
FROM ubuntu:22.04 # immagine di partenza
RUN apt update && apt install -y python3 # installa Python
COPY app.py /app/app.py # copia il codice
CMD ["python3", "/app/app.py"] # comando all'avvio
3. Architettura di Docker
Docker funziona con un'architettura client-server.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Host Linux β
β β
β βββββββββββββββ βββββββββββββββββββββββββββββ
β β Docker CLI βββββββββΊβ Docker Daemon ββ
β β (client) β API β (dockerd) ββ
β βββββββββββββββ REST β ββ
β β ββββββββββ ββββββββββ ββ
β β β Cont.1 β β Cont.2 β ββ
β β ββββββββββ ββββββββββ ββ
β βββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββ
β Docker Hub β
β (o altro reg.) β
ββββββββββββββββββββ
Docker CLI β il comando docker che usi nel terminale. Invia comandi al daemon tramite API REST.
Docker Daemon (dockerd) β il processo server che gira in background. Gestisce immagini, container, reti e volumi. Su Linux comunica tramite il socket /var/run/docker.sock.
containerd β il runtime dei container, usato dal daemon per creare e gestire effettivamente i container usando le syscall del kernel Linux (namespaces, cgroups).
Docker non Γ¨ magia: usa funzionalitΓ native del kernel Linux.
- Namespaces β isolano i processi, la rete, il filesystem, gli utenti: ogni container "vede" solo se stesso
- cgroups β limitano le risorse (CPU, RAM, I/O) assegnate a ogni container
- Union filesystem (OverlayFS) β implementa il sistema a layer delle immagini
4. Installazione su Fedora e RHEL
Fedora e RHEL includono docker nei propri repository, ma si tratta di Podman rinominato, non di Docker Engine originale. Per installare Docker Engine originale bisogna aggiungere il repository ufficiale di Docker.
4.1 Fedora (39, 40, 41, 42)
# Rimuovi eventuali versioni precedenti o conflittuali
sudo dnf remove -y docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
# Installa i prerequisiti
sudo dnf install -y dnf-plugins-core
# Aggiungi il repository ufficiale Docker
sudo dnf config-manager --add-repo \
https://download.docker.com/linux/fedora/docker-ce.repo
# Installa Docker Engine, CLI e containerd
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Avvia il daemon e abilitalo all'avvio
sudo systemctl start docker
sudo systemctl enable docker
# Verifica che funzioni
sudo docker run hello-world
4.2 RHEL / CentOS Stream / Rocky Linux / AlmaLinux
# Rimuovi versioni precedenti
sudo dnf remove -y docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
# Installa i prerequisiti
sudo dnf install -y yum-utils
# Aggiungi il repository ufficiale Docker per RHEL
sudo yum-config-manager --add-repo \
https://download.docker.com/linux/rhel/docker-ce.repo
# Installa Docker Engine
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Avvia il daemon
sudo systemctl start docker
sudo systemctl enable docker
# Verifica
sudo docker run hello-world
Docker Γ¨ compatibile con SELinux in modalitΓ Enforcing. Se noti problemi di accesso ai volumi, aggiungi il suffisso :z o :Z ai mount dei volumi:
docker run -v /mio/path:/app:z nome-immagine
# :z = volume condiviso tra container (label shared)
# :Z = volume privato di questo container (label private)
4.3 Verifica dell'installazione su RHEL-based
# Versione di Docker
docker --version
# Informazioni complete sul daemon
docker info
# Versione dettagliata client e server
docker version
# Stato del servizio
systemctl status docker
# Il daemon Γ¨ in ascolto sul socket?
ls -la /var/run/docker.sock
5. Installazione su Ubuntu e Debian
5.1 Ubuntu (20.04, 22.04, 24.04)
# Rimuovi versioni precedenti
sudo apt remove -y docker docker-engine docker.io containerd runc
# Aggiorna la lista dei pacchetti
sudo apt update
# Installa i prerequisiti per HTTPS
sudo apt install -y \
ca-certificates \
curl \
gnupg \
lsb-release
# Aggiungi la chiave GPG ufficiale di Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Aggiungi il repository Docker
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Aggiorna e installa Docker Engine
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Avvia e abilita il daemon
sudo systemctl start docker
sudo systemctl enable docker
# Verifica
sudo docker run hello-world
5.2 Debian (11 Bullseye, 12 Bookworm)
# Rimuovi versioni precedenti
sudo apt remove -y docker docker-engine docker.io containerd runc
sudo apt update
sudo apt install -y ca-certificates curl gnupg
# Aggiungi la chiave GPG
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Aggiungi il repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian \
$(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Installa
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl start docker
sudo systemctl enable docker
sudo docker run hello-world
6. Configurazione post-installazione
Questi passi sono uguali per tutte le distribuzioni e vanno fatti dopo l'installazione.
6.1 Usare Docker senza sudo
Per default il socket Docker Γ¨ accessibile solo da root. Per usare docker senza sudo bisogna aggiungere il proprio utente al gruppo docker.
# Aggiungi l'utente corrente al gruppo docker
sudo usermod -aG docker $USER
# Applica subito la modifica senza logout
newgrp docker
# Verifica (deve funzionare senza sudo)
docker run hello-world
Il gruppo docker dΓ accesso al socket del daemon, che gira come root. Un utente nel gruppo docker puΓ² di fatto ottenere privilegi root sul sistema host. Su server di produzione valuta se usare Rootless Docker o Podman (rootless by default).
6.2 Configurazione del daemon
Il daemon Docker si configura tramite /etc/docker/daemon.json.
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2",
"data-root": "/var/lib/docker",
"dns": ["8.8.8.8", "1.1.1.1"]
}
# Crea o modifica il file di configurazione
sudo nano /etc/docker/daemon.json
# Riavvia il daemon per applicare le modifiche
sudo systemctl restart docker
# Verifica che le modifiche siano attive
docker info | grep -i "storage\|log\|data"
6.3 Proxy per Docker (ambienti aziendali)
# Crea la directory di override per il servizio
sudo mkdir -p /etc/systemd/system/docker.service.d/
# Crea il file di configurazione del proxy
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf << EOF
[Service]
Environment="HTTP_PROXY=http://proxy.azienda.it:8080"
Environment="HTTPS_PROXY=http://proxy.azienda.it:8080"
Environment="NO_PROXY=localhost,127.0.0.1,*.azienda.it"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
7. I comandi essenziali
7.1 Immagini
# Scarica un'immagine dal registry
docker pull ubuntu:22.04
docker pull nginx:latest
docker pull postgres:16
# Elenca le immagini scaricate
docker images
docker image ls
# Cerca immagini su Docker Hub
docker search nginx
# Rimuovi un'immagine
docker rmi ubuntu:22.04
docker image rm ubuntu:22.04
# Rimuovi tutte le immagini non usate
docker image prune
docker image prune -a # rimuove anche quelle non taggate
# Informazioni dettagliate su un'immagine (storia dei layer)
docker inspect ubuntu:22.04
docker history ubuntu:22.04
# Salva un'immagine su file
docker save nginx:latest -o nginx-backup.tar
# Carica un'immagine da file
docker load -i nginx-backup.tar
# Tagga un'immagine
docker tag nginx:latest mio-nginx:personalizzato
7.2 Container β avvio e gestione
# Avvia un container interattivo (entra dentro)
docker run -it ubuntu:22.04 bash
# Avvia un container in background (detached)
docker run -d nginx:latest
# Avvia con nome, porta e variabili d'ambiente
docker run -d \
--name mio-nginx \
-p 8080:80 \
-e TZ=Europe/Rome \
nginx:latest
# Avvia con limite di risorse
docker run -d \
--name app-limitata \
--cpus="0.5" \
--memory="256m" \
nginx:latest
# Avvia e rimuovi automaticamente quando termina
docker run --rm ubuntu:22.04 echo "ciao e arrivederci"
# Avvia con policy di riavvio automatico
docker run -d \
--restart=always \
--name sempre-attivo \
nginx:latest
# Policy di restart disponibili:
# no = non riavviare mai (default)
# on-failure = riavvia solo se esce con errore
# always = riavvia sempre, anche dopo reboot host
# unless-stopped = sempre, tranne se fermato manualmente
7.3 Container β monitoraggio
# Elenca i container in esecuzione
docker ps
# Elenca tutti i container (anche fermi)
docker ps -a
# Elenca solo gli ID dei container in esecuzione
docker ps -q
# Log di un container
docker logs mio-nginx
docker logs -f mio-nginx # segui in tempo reale
docker logs --tail 50 mio-nginx # ultime 50 righe
docker logs --since 10m mio-nginx # ultimi 10 minuti
# Statistiche di utilizzo risorse in tempo reale
docker stats
docker stats mio-nginx
# Processi in esecuzione dentro un container
docker top mio-nginx
# Ispeziona tutta la configurazione di un container
docker inspect mio-nginx
# Informazioni generali su Docker e il sistema
docker info
docker system df # utilizzo disco di immagini, container, volumi
7.4 Container β interazione
# Entra in un container giΓ in esecuzione
docker exec -it mio-nginx bash
docker exec -it mio-nginx sh # se bash non Γ¨ disponibile
# Esegui un comando singolo senza entrare
docker exec mio-nginx ls /etc/nginx/
docker exec mio-nginx cat /etc/nginx/nginx.conf
# Copia file tra host e container
docker cp file.txt mio-nginx:/tmp/file.txt
docker cp mio-nginx:/etc/nginx/nginx.conf ./nginx.conf
# Ferma un container (SIGTERM, poi SIGKILL dopo 10s)
docker stop mio-nginx
# Ferma forzatamente (SIGKILL immediato)
docker kill mio-nginx
# Avvia un container fermato
docker start mio-nginx
# Riavvia un container
docker restart mio-nginx
# Metti in pausa (SIGSTOP)
docker pause mio-nginx
docker unpause mio-nginx
# Rimuovi un container fermato
docker rm mio-nginx
# Rimuovi forzatamente anche se in esecuzione
docker rm -f mio-nginx
# Rimuovi tutti i container fermati
docker container prune
7.5 Pulizia generale
# Rimuovi tutto il non utilizzato (container, reti, immagini dangling)
docker system prune
# Rimuovi tutto inclusi volumi e immagini non usate
docker system prune -a --volumes
# Pulizia selettiva
docker container prune # container fermi
docker image prune -a # immagini non usate
docker volume prune # volumi non usati
docker network prune # reti non usate
# Quanto spazio usa Docker?
docker system df
docker system df -v # dettagliato
8. Dockerfile β costruire immagini custom
8.1 Istruzioni principali
# βββ Istruzione di base βββββββββββββββββββββββββββββββββββββββ
FROM ubuntu:22.04
# Specifica l'immagine di partenza. Ogni Dockerfile deve iniziare con FROM.
# Usa "FROM scratch" per immagini completamente vuote.
# βββ Metadati ββββββββββββββββββββββββββββββββββββββββββββββββ
LABEL maintainer="mario@esempio.it"
LABEL version="1.0"
LABEL description="La mia applicazione"
# βββ Variabili d'ambiente βββββββββββββββββββββββββββββββββββββ
ENV APP_HOME=/opt/app
ENV NODE_ENV=production
ENV PORT=3000
# Accessibili durante il build E a runtime nel container.
# βββ Argomenti di build βββββββββββββββββββββββββββββββββββββββ
ARG VERSIONE=1.0
# Come ENV ma solo durante il build, non a runtime.
# Passato con: docker build --build-arg VERSIONE=2.0 .
# βββ Directory di lavoro ββββββββββββββββββββββββββββββββββββββ
WORKDIR /opt/app
# Imposta la directory corrente per le istruzioni successive.
# La crea se non esiste.
# βββ Esegui comandi durante il build ββββββββββββββββββββββββββ
RUN apt-get update && apt-get install -y \
curl \
git \
python3 \
&& rm -rf /var/lib/apt/lists/*
# Ogni RUN crea un nuovo layer. Concatena i comandi con &&
# per minimizzare i layer e la dimensione dell'immagine.
# βββ Copia file nell'immagine βββββββββββββββββββββββββββββββββ
COPY . .
# Copia file dall'host (contesto di build) nell'immagine.
ADD archivio.tar.gz /opt/
# Come COPY ma decomprime archivi automaticamente.
# Preferisci sempre COPY salvo casi specifici.
# βββ Utente βββββββββββββββββββββββββββββββββββββββββββββββββββ
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
USER appuser
# Non girare mai come root nei container di produzione.
# βββ Porte ββββββββββββββββββββββββββββββββββββββββββββββββββββ
EXPOSE 3000
# Documenta la porta che l'app usa. Non la pubblica davvero
# (quello si fa con -p in docker run).
# βββ Volumi βββββββββββββββββββββββββββββββββββββββββββββββββββ
VOLUME ["/opt/app/data"]
# Crea un punto di mount per i dati persistenti.
# βββ Healthcheck ββββββββββββββββββββββββββββββββββββββββββββββ
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# βββ Comando di avvio βββββββββββββββββββββββββββββββββββββββββ
ENTRYPOINT ["node"]
# Il comando "fisso" del container. Non sovrascrivibile da docker run
# (solo con --entrypoint). Ideale per il binario principale.
CMD ["server.js"]
# Argomenti di default per ENTRYPOINT, o il comando se ENTRYPOINT
# non Γ¨ definito. Sovrascrivibile passando argomenti a docker run.
8.2 Esempio completo β App Node.js
# Build stage β compila l'applicazione
FROM node:20-alpine AS builder
WORKDIR /build
# Copia prima i file delle dipendenze per sfruttare la cache dei layer
COPY package*.json ./
RUN npm ci --only=production
# Poi copia il codice sorgente
COPY src/ ./src/
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Production stage β immagine finale minimale
FROM node:20-alpine AS production
# Aggiorna i pacchetti di sistema per sicurezza
RUN apk update && apk upgrade && apk add --no-cache tini
# Crea utente non-root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# Copia solo l'output del build stage (non il codice sorgente)
COPY --from=builder /build/node_modules ./node_modules
COPY --from=builder /build/src ./src
COPY package.json ./
# Cambia proprietario dei file
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
# tini come PID 1 per gestire correttamente i segnali
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "src/server.js"]
8.3 Il file .dockerignore
Come .gitignore ma per il contesto di build. Riduce la dimensione del contesto e impedisce di copiare file inutili nell'immagine.
# .dockerignore
node_modules/
.git/
.gitignore
*.log
.env
.env.local
dist/
coverage/
.DS_Store
README.md
docker-compose*.yml
Dockerfile*
8.4 Comandi docker build
# Build base (cerca Dockerfile nella directory corrente)
docker build -t mia-app:1.0 .
# Build da Dockerfile con nome diverso
docker build -f Dockerfile.prod -t mia-app:prod .
# Build con argomenti
docker build --build-arg VERSIONE=2.0 -t mia-app:2.0 .
# Build con target stage specifico (multi-stage)
docker build --target builder -t mia-app:builder .
# Build senza cache (forza il rebuild di tutti i layer)
docker build --no-cache -t mia-app:latest .
# Build e push in un colpo solo
docker build -t miouser/mia-app:latest . && docker push miouser/mia-app:latest
# Ispeziona i layer e la storia dell'immagine
docker history mia-app:1.0
9. Docker Compose
Docker Compose permette di definire e avviare applicazioni multi-container con un singolo file YAML.
9.1 Struttura del file compose
# docker-compose.yml
version: "3.9"
services:
# βββ Applicazione web βββββββββββββββββββββββββββββββββββββββ
webapp:
build:
context: .
dockerfile: Dockerfile
image: mia-webapp:latest
container_name: webapp
restart: unless-stopped
ports:
- "8080:3000"
environment:
- NODE_ENV=production
- DB_HOST=database
- DB_PORT=5432
- DB_NAME=mioapp
- DB_USER=appuser
- DB_PASSWORD=${DB_PASSWORD} # da file .env
volumes:
- ./uploads:/app/uploads
- ./logs:/app/logs
depends_on:
database:
condition: service_healthy
redis:
condition: service_started
networks:
- app-network
labels:
- "com.esempio.service=webapp"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
# βββ Database PostgreSQL βββββββββββββββββββββββββββββββββββββ
database:
image: postgres:16-alpine
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_DB: mioapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432" # rimuovi in produzione
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d mioapp"]
interval: 10s
timeout: 5s
retries: 5
# βββ Cache Redis βββββββββββββββββββββββββββββββββββββββββββββ
redis:
image: redis:7-alpine
container_name: redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis-data:/data
networks:
- app-network
# βββ Reverse proxy Nginx βββββββββββββββββββββββββββββββββββββ
nginx:
image: nginx:alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- webapp
networks:
- app-network
# βββ Volumi persistenti ββββββββββββββββββββββββββββββββββββββ
volumes:
postgres-data:
driver: local
redis-data:
driver: local
# βββ Reti ββββββββββββββββββββββββββββββββββββββββββββββββββββ
networks:
app-network:
driver: bridge
9.2 Comandi Docker Compose
# Avvia tutti i servizi (build se necessario)
docker compose up
# Avvia in background
docker compose up -d
# Avvia solo un servizio specifico
docker compose up -d database
# Avvia ricostruendo le immagini
docker compose up -d --build
# Ferma tutti i servizi
docker compose stop
# Ferma e rimuovi container, reti (NON i volumi)
docker compose down
# Ferma, rimuovi container, reti E volumi
docker compose down -v
# Ferma, rimuovi E rimuovi le immagini
docker compose down --rmi all
# Stato dei servizi
docker compose ps
# Log di tutti i servizi
docker compose logs
docker compose logs -f # segui in tempo reale
docker compose logs -f webapp # solo un servizio
# Entra in un container
docker compose exec webapp bash
docker compose exec database psql -U appuser -d mioapp
# Esegui un comando una-tantum (crea un container temporaneo)
docker compose run --rm webapp npm run migrate
# Scala un servizio (piΓΉ istanze)
docker compose up -d --scale webapp=3
# Rebuild solo di un servizio
docker compose build webapp
docker compose up -d --no-deps --build webapp
10. Networking in Docker
10.1 Tipi di rete
# Elenca le reti esistenti
docker network ls
# Le reti di default:
# bridge = rete isolata per container sullo stesso host
# host = il container usa direttamente la rete dell'host
# none = nessuna rete (container completamente isolato)
Bridge (default) β Ogni container ha il suo IP privato in una sottorete interna. I container sulla stessa rete bridge si parlano tramite IP o nome container.
Host β Il container condivide lo stack di rete dell'host. Nessun isolamento di rete, ma massime performance. Usato per applicazioni che richiedono accesso diretto alle interfacce di rete.
None β Il container non ha alcuna interfaccia di rete. Usato per task di elaborazione isolati.
10.2 Gestione delle reti
# Crea una rete bridge personalizzata
docker network create mia-rete
# Crea con subnet specifica
docker network create \
--driver bridge \
--subnet 172.20.0.0/16 \
--ip-range 172.20.240.0/20 \
mia-rete-custom
# Avvia un container collegato a una rete specifica
docker run -d --network mia-rete --name app nginx
# Collega un container in esecuzione a una rete
docker network connect mia-rete container-esistente
# Scollega un container da una rete
docker network disconnect mia-rete container-esistente
# Ispeziona una rete (vedi container collegati, subnet, ecc.)
docker network inspect mia-rete
# Rimuovi una rete
docker network rm mia-rete
# Rimuovi tutte le reti non usate
docker network prune
Nelle reti bridge create dall'utente (non quella default), Docker fornisce un DNS interno: i container si raggiungono usando il nome del container come hostname. Ad esempio, se hai un container database, un altro container nella stessa rete puΓ² connettersi usando database:5432 come host.
10.3 Pubblicare porte
# Pubblica la porta 80 del container sulla porta 8080 dell'host
docker run -p 8080:80 nginx
# Pubblica su tutte le interfacce dell'host
docker run -p 0.0.0.0:8080:80 nginx
# Pubblica solo su localhost (piΓΉ sicuro)
docker run -p 127.0.0.1:8080:80 nginx
# Pubblica UDP
docker run -p 5353:5353/udp dns-server
# Pubblica su una porta casuale dell'host
docker run -p 80 nginx
docker port container-name # scopri su quale porta Γ¨ finito
11. Volumi e persistenza dei dati
I container sono effimeri: quando un container viene rimosso, tutti i suoi dati vengono persi. I volumi risolvono questo problema.
11.1 Tipi di storage
Volume Docker β gestito direttamente da Docker, memorizzato in /var/lib/docker/volumes/. Raccomandato per la persistenza dei dati.
Bind Mount β monta una directory specifica dell'host nel container. Utile in sviluppo per condividere il codice sorgente.
tmpfs Mount β dati in memoria RAM, non persistiti sul disco. Utile per dati sensibili temporanei.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Host β
β β
β /var/lib/docker/volumes/ /mio/progetto/ β
β β β β
β Volume Docker Bind Mount β
β β β β
β ββββββββββΌβββββββββββββββββββββββββΌβββββββββββββββ β
β β Container β β
β β /app/data /app/src β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
11.2 Gestione dei volumi
# Crea un volume
docker volume create miei-dati
# Crea un volume con driver specifico
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=nfs-server,rw \
--opt device=:/path/to/data \
volume-nfs
# Elenca i volumi
docker volume ls
# Ispeziona un volume
docker volume inspect miei-dati
# Rimuovi un volume
docker volume rm miei-dati
# Rimuovi tutti i volumi non usati
docker volume prune
# Avvia un container con un volume Docker
docker run -d \
-v miei-dati:/app/data \
--name app \
mia-app
# Avvia con un bind mount (directory dell'host)
docker run -d \
-v /home/utente/progetto:/app \
--name dev-app \
mia-app
# Bind mount in sola lettura
docker run -d \
-v /etc/config:/app/config:ro \
mia-app
# Avvia con tmpfs (in memoria)
docker run -d \
--tmpfs /app/temp:size=100m \
mia-app
11.3 Backup e ripristino di volumi
# Backup di un volume in un file tar
docker run --rm \
-v miei-dati:/source:ro \
-v $(pwd):/backup \
ubuntu \
tar czf /backup/backup-$(date +%Y%m%d).tar.gz -C /source .
# Ripristino da backup
docker run --rm \
-v miei-dati:/target \
-v $(pwd):/backup \
ubuntu \
tar xzf /backup/backup-20260601.tar.gz -C /target
12. π§ͺ Laboratorio pratico
Gli esercizi sono progressivi. Ogni esercizio si basa sul precedente. Esegui i comandi, osserva l'output, poi rispondi mentalmente alla domanda. Le soluzioni sono in callout chiudibili.
Esercizio 1 β Il tuo primo container
Obiettivo: capire il ciclo di vita base di un container.
# Passo 1: scarica l'immagine di Ubuntu
docker pull ubuntu:22.04
# Passo 2: avvia un container interattivo
docker run -it --name lab-ubuntu ubuntu:22.04 bash
# Dentro il container, esegui:
cat /etc/os-release
hostname
ps aux
ls /
exit
# Passo 3: guarda i container (anche quelli fermati)
docker ps -a
# Passo 4: riavvia il container e rientra
docker start lab-ubuntu
docker exec -it lab-ubuntu bash
# Dentro: crea un file
echo "ciao da Docker" > /tmp/test.txt
cat /tmp/test.txt
exit
# Passo 5: rimuovi il container
docker rm -f lab-ubuntu
# Passo 6: avvia di nuovo un container ubuntu
docker run -it --rm ubuntu:22.04 bash
# Cerca /tmp/test.txt β esiste ancora?
ls /tmp/
- I container sono isolati dal sistema host
- I dati dentro un container non persistono dopo la sua rimozione
docker execpermette di entrare in un container giΓ in esecuzione--rmrimuove automaticamente il container quando termina
Esercizio 2 β Nginx come web server
Obiettivo: esporre un servizio web, mappare porte, servire contenuto custom.
# Passo 1: avvia Nginx sulla porta 8080
docker run -d --name lab-nginx -p 8080:80 nginx:latest
# Passo 2: verifica che funzioni
curl http://localhost:8080
# oppure apri il browser su http://localhost:8080
# Passo 3: guarda i log in tempo reale
docker logs -f lab-nginx
# (esegui il curl di nuovo in un altro terminale)
# Passo 4: ispeziona il container
docker inspect lab-nginx | grep -A 10 "IPAddress"
# Passo 5: crea una pagina HTML custom
mkdir -p ~/docker-lab/html
cat > ~/docker-lab/html/index.html << 'EOF'
<!DOCTYPE html>
<html>
<body style="font-family: sans-serif; text-align: center; padding: 50px">
<h1>π³ Il mio primo container Nginx!</h1>
<p>Servito da Docker β funziona!</p>
</body>
</html>
EOF
# Passo 6: rimuovi il container e ricrealo con bind mount
docker rm -f lab-nginx
docker run -d \
--name lab-nginx \
-p 8080:80 \
-v ~/docker-lab/html:/usr/share/nginx/html:ro \
nginx:latest
# Passo 7: verifica la pagina custom
curl http://localhost:8080
# Passo 8: modifica il file HTML sull'host
echo "<p>Aggiornato!</p>" >> ~/docker-lab/html/index.html
curl http://localhost:8080 # la modifica Γ¨ immediata, senza restart
# Pulizia
docker rm -f lab-nginx
- Come pubblicare porte con
-p host:container - Come servire file dall'host con bind mount
-v - Le modifiche ai bind mount sono immediate nel container
Esercizio 3 β Costruire un'immagine personalizzata
Obiettivo: scrivere un Dockerfile e costruire la tua prima immagine.
# Passo 1: crea la directory del progetto
mkdir -p ~/docker-lab/app
cd ~/docker-lab/app
# Passo 2: crea l'applicazione Python
cat > app.py << 'EOF'
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import datetime
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {
"messaggio": "Ciao da Docker!",
"ora": str(datetime.datetime.now()),
"path": self.path
}
self.wfile.write(json.dumps(response, indent=2).encode())
def log_message(self, format, *args):
print(f"[{datetime.datetime.now()}] {args[0]} {args[1]} {args[2]}")
print("Server avviato su porta 8000...")
HTTPServer(('0.0.0.0', 8000), Handler).serve_forever()
EOF
# Passo 3: crea il Dockerfile
cat > Dockerfile << 'EOF'
FROM python:3.12-slim
WORKDIR /app
COPY app.py .
EXPOSE 8000
CMD ["python3", "app.py"]
EOF
# Passo 4: crea il .dockerignore
cat > .dockerignore << 'EOF'
__pycache__/
*.pyc
.git/
EOF
# Passo 5: costruisci l'immagine
docker build -t mia-app-python:1.0 .
# Passo 6: guarda l'immagine creata
docker images mia-app-python
# Passo 7: guarda i layer
docker history mia-app-python:1.0
# Passo 8: avvia il container
docker run -d --name lab-python -p 8000:8000 mia-app-python:1.0
# Passo 9: testa l'applicazione
curl http://localhost:8000
curl http://localhost:8000/hello
curl http://localhost:8000/docker-lab
# Passo 10: guarda i log
docker logs lab-python
# Pulizia
docker rm -f lab-python
- Come scrivere un
Dockerfilebase - Come costruire un'immagine con
docker build - Come l'immagine Γ¨ composta da layer
- Come avviare e testare un container dalla tua immagine
Esercizio 4 β Stack completo con Docker Compose
Obiettivo: mettere in piedi uno stack WordPress + MySQL con Compose.
# Passo 1: crea la directory
mkdir -p ~/docker-lab/wordpress
cd ~/docker-lab/wordpress
# Passo 2: crea il file .env con le password
cat > .env << 'EOF'
MYSQL_ROOT_PASSWORD=rootpassword123
MYSQL_DATABASE=wordpress
MYSQL_USER=wpuser
MYSQL_PASSWORD=wppassword123
EOF
# Passo 3: crea il docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: "3.9"
services:
database:
image: mysql:8.0
container_name: wp-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
networks:
- wp-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
wordpress:
image: wordpress:latest
container_name: wp-app
restart: unless-stopped
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: database:3306
WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
WORDPRESS_DB_USER: ${MYSQL_USER}
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- wp-content:/var/www/html/wp-content
depends_on:
database:
condition: service_healthy
networks:
- wp-network
volumes:
mysql-data:
wp-content:
networks:
wp-network:
driver: bridge
EOF
# Passo 4: avvia lo stack
docker compose up -d
# Passo 5: monitora l'avvio
docker compose logs -f
# Passo 6: aspetta che sia tutto up
docker compose ps
# Passo 7: quando tutto Γ¨ "healthy", apri il browser
echo "Apri http://localhost:8080 per installare WordPress"
# Passo 8: entra nel database e verifica
docker compose exec database mysql -u wpuser -pwppassword123 wordpress -e "SHOW TABLES;"
# Passo 9: scala il servizio wordpress (prova β serve load balancer per funzionare bene)
docker compose up -d --scale wordpress=2
# Passo 10: ferma tutto (i dati rimangono nei volumi)
docker compose stop
# Passo 11: riavvia β i dati sono ancora lΓ¬
docker compose start
# Pulizia completa (rimuove anche i volumi!)
docker compose down -v
- Come orchestrare piΓΉ container con un singolo file
- Come usare variabili d'ambiente da file
.env - Come le dipendenze tra servizi si definiscono con
depends_on - Come i volumi Docker persistono i dati tra restart
- Come scalare un servizio
Esercizio 5 β Debugging e troubleshooting
Obiettivo: imparare a diagnosticare e risolvere problemi nei container.
# βββ Scenario 1: container che non parte βββββββββββββββββββββ
# Crea un container con un errore intenzionale
docker run -d --name broken-app \
-e DB_HOST=non-esiste \
postgres:16
# Diagnosi
docker ps -a # stato del container
docker logs broken-app # leggi i log
docker inspect broken-app | grep -A5 '"State"'
# βββ Scenario 2: container che si mangia la RAM βββββββββββββββ
# Avvia un container senza limiti
docker run -d --name hungry-app nginx:latest
# Monitora le risorse
docker stats hungry-app --no-stream # snapshot
docker stats hungry-app # live
# Riavvia con limiti di risorse
docker rm -f hungry-app
docker run -d --name hungry-app \
--memory="128m" \
--cpus="0.25" \
nginx:latest
docker stats hungry-app --no-stream # vedi i limiti applicati
# βββ Scenario 3: container senza shell βββββββββββββββββββββββ
# Alcune immagini minimali non hanno bash
docker run -d --name no-shell alpine:latest sleep 1000
# ash Γ¨ disponibile su Alpine
docker exec -it no-shell ash
# Se proprio non c'Γ¨ shell, usa nsenter
PID=$(docker inspect --format '{{.State.Pid}}' no-shell)
sudo nsenter -t $PID -m -u -i -n -p -- sh
# βββ Scenario 4: ispezionare i layer di un'immagine ββββββββββ
docker history nginx:latest
docker inspect nginx:latest | python3 -m json.tool | head -50
# βββ Scenario 5: file system del container βββββββββββββββββββ
# Vedi le differenze tra il filesystem del container e l'immagine base
docker run -d --name diff-test nginx:latest
# Crea qualche file dentro
docker exec diff-test touch /tmp/nuovo-file.txt
docker exec diff-test bash -c "echo test > /etc/test.conf"
# Vedi cosa Γ¨ cambiato
docker diff diff-test
# A = Added, C = Changed, D = Deleted
# βββ Scenario 6: esportare e importare un container ββββββββββ
# Esporta il filesystem del container (snapshot)
docker export diff-test -o snapshot.tar
# Importa come nuova immagine
docker import snapshot.tar mio-snapshot:latest
docker run --rm mio-snapshot:latest ls /tmp/
# Pulizia
docker rm -f broken-app hungry-app no-shell diff-test
rm -f snapshot.tar
- Come leggere i log e diagnosticare container che non partono
- Come monitorare e limitare l'uso di risorse
- Come ispezionare il filesystem di un container con
docker diff - Come lavorare con container che non hanno shell
- Come esportare/importare lo stato di un container
Esercizio 6 β Rete tra container
Obiettivo: capire come i container comunicano tra loro.
# Passo 1: crea una rete custom
docker network create lab-network
# Passo 2: avvia un server Redis
docker run -d \
--name lab-redis \
--network lab-network \
redis:7-alpine
# Passo 3: avvia un client e testa la comunicazione
docker run -it --rm \
--network lab-network \
redis:7-alpine \
redis-cli -h lab-redis ping
# Deve rispondere: PONG
# Passo 4: scrivi e leggi dati su Redis
docker run -it --rm \
--network lab-network \
redis:7-alpine \
redis-cli -h lab-redis SET chiave "valore dal container"
docker run -it --rm \
--network lab-network \
redis:7-alpine \
redis-cli -h lab-redis GET chiave
# Passo 5: dimostra l'isolamento tra reti diverse
docker run -d --name fuori-rete nginx:latest # nessuna rete custom
docker run -it --rm \
--network lab-network \
redis:7-alpine \
redis-cli -h fuori-rete ping
# Deve fallire: non risolve il nome
# Passo 6: collega il container alla rete (a posteriori)
docker network connect lab-network fuori-rete
docker run -it --rm \
--network lab-network \
redis:7-alpine \
ping -c 3 fuori-rete
# Ora funziona
# Passo 7: ispeziona la rete
docker network inspect lab-network
# Pulizia
docker rm -f lab-redis fuori-rete
docker network rm lab-network
- I container sullo stesso network si raggiungono per nome
- Container su reti diverse sono isolati tra loro
- Si puΓ² connettere un container a una rete anche dopo l'avvio
Checklist finale del laboratorio
Dopo aver completato tutti gli esercizi dovresti essere in grado di:
Riferimenti rapidi
# βββ IMMAGINI ββββββββββββββββββββββββββββββββββββββββββββββββ
docker pull IMAGE # scarica immagine
docker images # elenca immagini
docker build -t TAG . # costruisci immagine
docker rmi IMAGE # rimuovi immagine
docker image prune -a # rimuovi immagini non usate
# βββ CONTAINER βββββββββββββββββββββββββββββββββββββββββββββββ
docker run -d -p H:C --name N IMAGE # avvia in background
docker run -it --rm IMAGE bash # interattivo e usa-e-getta
docker ps -a # elenca tutti i container
docker logs -f NAME # log in tempo reale
docker exec -it NAME bash # entra in container
docker stop/start/rm NAME # ferma, avvia, rimuovi
docker stats # utilizzo risorse live
docker inspect NAME # info complete
# βββ COMPOSE βββββββββββββββββββββββββββββββββββββββββββββββββ
docker compose up -d # avvia stack in background
docker compose down -v # ferma e rimuovi tutto
docker compose logs -f # log di tutti i servizi
docker compose exec S bash # entra nel servizio S
docker compose ps # stato dei servizi
# βββ SISTEMA βββββββββββββββββββββββββββββββββββββββββββββββββ
docker system df # utilizzo disco
docker system prune -a # pulizia completa
docker network ls # elenca reti
docker volume ls # elenca volumi
Dopo aver padroneggiato Docker Engine, i temi successivi naturali sono:
- Docker Swarm β orchestrazione multi-host nativa di Docker
- Kubernetes β orchestrazione enterprise per container in produzione
- Podman β alternativa rootless e daemonless a Docker
- Buildah/Skopeo β strumenti avanzati per build e gestione immagini
- Harbor β registry privato self-hosted