Se sei un sysadmin e hai resistito a Docker finora, capisco la diffidenza. Docker nasce con un'aura "dev-friendly" che a volte fa sembrare lo strumento adatto solo per chi scrive codice. In realtà è uno degli strumenti più utili che puoi aggiungere al tuo arsenale operativo, specialmente se gestisci server Linux in ambienti PMI o MSP.
Questa guida è scritta per chi conosce Linux, sa usare il terminale, e vuole capire come portare Docker in produzione senza perdere il controllo di quello che gira sui propri server.
Installazione su Linux (senza Docker Desktop)
Docker Desktop non serve su Linux. Serve sul Mac o Windows quando non hai accesso diretto al kernel Linux. Su un server Ubuntu o Debian, installi direttamente il demone:
# Ubuntu 22.04 / 24.04
curl -fsSL https://get.docker.com | sh
# Aggiungi il tuo utente al gruppo docker (eviti sudo ogni volta)
sudo usermod -aG docker $USER
newgrp docker
# Verifica
docker versionLo script ufficiale di Docker è quello che si usa in produzione. Non usare il pacchetto docker.io dai repo Ubuntu: è vecchio e mancano le ultime feature.
I quattro concetti che devi capire (e solo questi)
Docker ha una terminologia che all'inizio sembra più complicata di quello che è. Ti servono solo quattro concetti:
- Image: il blueprint del container, read-only. Come un template VM, ma leggero.
- Container: istanza in esecuzione di un'image. Puoi averne cento dalla stessa image.
- Volume: dove metti i dati persistenti. Tutto il resto del container è effimero.
- Network: come i container parlano tra loro (e con l'esterno).
Tutto il resto, registry, layer, overlay filesystem, viene da sé una volta che hai questi chiari.
Docker Compose: lo strumento reale per la produzione
Nessuno gestisce container in produzione con comandi docker run lanciati a mano. Lo strumento che usi ogni giorno si chiama Docker Compose, ed è incluso in Docker moderno (docker compose, senza trattino).
Un file compose.yml descrive tutto ciò che serve per far girare un'applicazione:
services:
app:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./html:/usr/share/nginx/html:ro
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
db:
image: postgres:16
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
postgres_data:Con questo file, avvii tutto con docker compose up -d e spegni tutto con docker compose down. Semplice da versionare su Git, semplice da replicare su un altro server.
Restart policy: fondamentale, spesso ignorata
Se un container crasha e non hai configurato la restart policy, rimane giù finché non lo riavvii a mano. In produzione questo non è accettabile.
Le opzioni che usi in pratica:
restart: unless-stoppedriavvia sempre, tranne se l'hai fermato tu esplicitamente condocker stop. Quello che vuoi in produzione.restart: alwaysriavvia sempre, anche dopo undocker stop. Utile se vuoi che riparta al boot automaticamente.restart: on-failureriavvia solo se esce con codice di errore. Per job e cron container.restart: nonon riavvia mai. Default. Non usarlo in produzione.
Logging: non riempire il disco
Il problema più comune sui server Docker in produzione è il disco che si riempie di log. Per default Docker accumula i log JSON senza limiti. Soluzione: configurare il log driver globalmente.
Modifica /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}Poi ricarica Docker:
sudo systemctl reload dockerI nuovi container useranno questi limiti automaticamente. I container esistenti vanno ricreati per applicare la modifica.
Se hai già uno stack di log centralizzato (Loki, ELK), puoi usare i driver loki o gelf argomento per un articolo dedicato.
Volumi: dove finiscono i tuoi dati
Regola fondamentale: tutto ciò che deve sopravvivere al container va su un volume. I container sono usa e getta, l'image si aggiorna, il container si ricrea, i dati restano sul volume.
Due modi per montare dati:
# Bind mount: mappa una cartella dell'host nel container
volumes:
- /opt/myapp/config:/app/config
# Named volume: gestito da Docker, preferibile per database
volumes:
- postgres_data:/var/lib/postgresql/dataPer i database usa sempre named volume. Per file di configurazione che vuoi versionare su Git, il bind mount va bene.
Backup di un named volume:
docker run --rm \
-v postgres_data:/data \
-v $(pwd):/backup \
alpine \
tar czf /backup/postgres_backup_$(date +%Y%m%d).tar.gz -C /data .Sicurezza base: quello che non puoi ignorare
Docker di default è troppo permissivo per la produzione. Tre regole da applicare sempre:
1. Non girare come root dentro il container
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser2. Mai --privileged senza una ragione precisa--privileged dà al container accesso completo all'host. Se un container compromesso gira con questo flag, l'attaccante ha root sul server.
3. Filesystem read-only dove possibile
services:
app:
image: myapp:latest
read_only: true
tmpfs:
- /tmp
volumes:
- app_data:/app/dataComandi essenziali per il troubleshooting
# Tutti i container (anche quelli fermi)
docker ps -a
# Log in real-time
docker logs -f nome_container
# Shell dentro il container per debug
docker exec -it nome_container sh
# Uso risorse in real-time (come top, ma per container)
docker stats
# Configurazione completa del container
docker inspect nome_container
# Quanto spazio occupa Docker
docker system df
# Pulizia: immagini, container fermi, volumi orfani
# ATTENZIONE: rimuove anche i volumi non montati
docker system prune -a --volumesPattern da non dimenticare in produzione
- Un servizio per container non mettere nginx + app + database nello stesso container. Rompe scaling e aggiornamento indipendente.
- Tag specifici, mai
latestpostgres:latestè una bomba a orologeria. Usapostgres:16.3e aggiorna con controllo. - Segreti fuori dal Compose file usa
.envfile escluso da Git, o Docker Secrets in ambienti multi-nodo. - Health check sempre definito:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40sSenza health check, Docker considera il container "up" non appena il processo parte, anche se l'applicazione ci mette 30 secondi ad inizializzarsi o è in crash loop.
Conclusione
Docker in produzione non è complicato se parti dai concetti giusti: Compose per orchestrare, volumi per i dati, restart policy sempre configurata, logging con limiti, sicurezza minima ma presente.
Non serve Kubernetes per la maggior parte dei workload di una PMI o di un MSP. Docker Compose su un server Linux ben configurato copre il 90% dei casi d'uso, con molta meno complessità operativa.
Nel prossimo articolo vedremo come monitorare i container Docker con Zabbix: metriche, alert e LLD per scoprire automaticamente i container che girano sul server.