Un Aggiornamento Importante del Docker Engine su un Server di Produzione Live
Aggiornare Docker Engine su un server che esegue attivamente più di cento container non è un compito che premia l'improvvisazione. Quando è arrivato il momento di passare da Docker Engine 28 a 29 e da Docker Compose v2 a v5 sul nostro server di produzione Hetzner, l'abbiamo affrontato con la stessa disciplina che applicheremmo a una migrazione di database: una checklist di preparazione scritta, un percorso di rollback testato, una sequenza di esecuzione precisa e una fase di verifica post-aggiornamento prima di dichiarare la finestra chiusa.
Ciò che segue è un resoconto completo di come l'abbiamo affrontato — il ragionamento dietro ogni passaggio, i comandi che abbiamo effettivamente eseguito e a cosa prestare attenzione quando il numero di container è abbastanza alto da far sì che una policy di riavvio mal configurata possa cascadare in un recupero difficile.
Perché Questo Aggiornamento Richiedeva un Runbook Formale
Docker Engine 29 ha introdotto modifiche al percorso di integrazione di containerd e ha adeguato il modo in cui docker compose gestisce la risoluzione delle dipendenze nei blocchi depends_on con condition: service_healthy. Docker Compose v5, nel frattempo, è una riscrittura interna significativa — passando dal binario v2 basato su Go a una nuova architettura che risolve diversi problemi di lunga data con l'ordinamento dell'avvio parallelo, ma che depreca anche una manciata di direttive del file Compose che v2 ignorava silenziosamente.
Su un server dove un singolo docker-compose.yml potrebbe governare dodici servizi interdipendenti — database, reverse proxy, container applicativi, worker in background — un cambiamento comportamentale silenzioso nella risoluzione delle dipendenze è esattamente il tipo di problema che non emerge immediatamente. Emerge alle 03:00 quando un healthcheck non si attiva mai e un container dipendente va in loop indefinitamente.
La risposta pratica è la preparazione, non la cautela fine a se stessa.
Checklist di Preparazione
Prima di toccare un singolo pacchetto, abbiamo seguito la seguente checklist. Ogni elemento ha una ragione.
1. Fare l'inventario di tutti i container in esecuzione e delle loro policy di riavvio.
docker ps --format "table {{.Names}} {{.Status}} {{.Image}}" | sort
docker inspect $(docker ps -q) --format '{{.Name}} restart={{.HostConfig.RestartPolicy.Name}}' | sort
Questo fornisce una baseline. I container con restart: always tenteranno di tornare attivi automaticamente dopo il riavvio del daemon Docker — che è ciò che si vuole, ma solo se i file compose e le immagini sottostanti sono in uno stato noto e funzionante. Qualsiasi container in un crash-loop al momento dell'aggiornamento sarà ancora in un crash-loop successivamente, ed è meglio saperlo ora.
2. Validare tutti i file compose rispetto allo schema v5 prima dell'aggiornamento.
docker compose config --quiet 2>&1 | grep -i warning
Eseguire questo in ogni directory che contiene un docker-compose.yml. Compose v5 è più severo riguardo alle chiavi deprecate — in particolare version: in cima ai file compose (ora ignorato ma genera un avviso), e qualsiasi uso della direttiva links: deprecata. Gli avvisi in v5 possono diventare errori nelle release successive; vale la pena risolverli ora.
3. Confermare lo spazio disco disponibile.
df -h /var/lib/docker
docker system df
L'aggiornamento scaricherà un nuovo shim containerd e sostituirà il binario Docker Engine. Il vecchio binario e le sue dipendenze non vengono sempre eliminati automaticamente. Su un server che è in esecuzione da un anno, docker system df spesso rivela diversi gigabyte di layer di immagini recuperabili. Pulire questi prima dell'aggiornamento, non durante.
docker system prune -f --volumes
Usare --volumes solo se si è certi che nessun volume anonimo contenga dati necessari. Sul nostro server, tutti i dati persistenti sono montati da volumi nominati, dichiarati esplicitamente — quindi era sicuro.
4. Esportare la versione corrente di Docker Engine e compose in un file di riferimento.
docker version > /root/docker-pre-upgrade.txt
docker compose version >> /root/docker-pre-upgrade.txt
docker ps -a >> /root/docker-pre-upgrade.txt
Questo richiede dieci secondi e fornisce un riferimento di rollback inequivocabile se qualcosa va storto e si ha bisogno di identificare quali container erano in esecuzione al momento dell'aggiornamento.
5. Notificare i sistemi downstream.
Il nostro server esegue uno stack Supabase self-hosted e diverse istanze di automazione dei flussi di lavoro n8n. Qualsiasi webhook che si attivi durante un ciclo di riavvio dei container fallirà silenziosamente sul lato mittente. Abbiamo impostato una finestra di manutenzione nel nostro strumento di monitoraggio dell'uptime e disabilitato i webhook in entrata in n8n per la durata.
Il Piano di Rollback
Un piano di rollback è utile solo se si è deciso in anticipo quale condizione lo attiva. Il nostro era semplice: se qualsiasi container che era in esecuzione prima dell'aggiornamento non è in esecuzione dieci minuti dopo il completamento dell'aggiornamento e non può essere recuperato con un docker compose up -d, si fa il rollback del binario Docker Engine.
Fare il rollback di Docker Engine su un sistema Debian significa fissare la versione precedente del pacchetto. Abbiamo annotato la stringa di versione esatta prima di iniziare:
apt-cache policy docker-ce docker-ce-cli containerd.io docker-compose-plugin
Output sul nostro server prima dell'aggiornamento:
docker-ce:
Installed: 5:28.1.1-1~debian.12~bookworm
Candidate: 5:29.0.1-1~debian.12~bookworm
docker-compose-plugin:
Installed: 2.35.1-1~debian.12~bookworm
Candidate: 2.36.0-1~debian.12~bookworm
Il comando di rollback, nel caso fosse necessario:
apt-get install -y docker-ce=5:28.1.1-1~debian.12~bookworm docker-ce-cli=5:28.1.1-1~debian.12~bookworm containerd.io docker-compose-plugin=2.35.1-1~debian.12~bookworm
Abbiamo mantenuto questo comando in un file di testo sul server in /root/docker-rollback.sh con permessi di esecuzione, pronto per essere eseguito senza digitare sotto pressione.
Il Processo di Aggiornamento
L'aggiornamento effettivo è semplice una volta completata la preparazione. La sequenza è importante: prima aggiornare l'indice dei pacchetti, verificare cosa verrà installato, poi installare.
Passaggio 1: Aggiornare l'indice apt solo per il repository Docker.
apt-get update -o Dir::Etc::sourcelist="sources.list.d/docker.list" -o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"
Questo limita l'aggiornamento al repository Docker piuttosto che aggiornare tutte le sorgenti. Su un server di produzione, un aggiornamento di pacchetti non intenzionale da una sorgente non correlata durante una finestra di manutenzione Docker è una variabile di cui non si ha bisogno.
Passaggio 2: Confermare le versioni candidate.
apt-cache policy docker-ce docker-ce-cli containerd.io docker-compose-plugin
Verificare che il candidato corrisponda a ciò che si si aspetta prima di procedere. Se il candidato mostra una versione più recente di quella testata in staging, fare una pausa e valutare.
Passaggio 3: Installare.
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
Questo fermerà il daemon Docker, sostituirà i binari e riavvierà il daemon. I container con restart: always verranno riportati attivi dal daemon al riavvio. La finestra in cui i container non sono in esecuzione è tipicamente inferiore a trenta secondi su un server di dimensioni ragionevoli — anche se varia a seconda di quanto tempo impiega containerd per inizializzarsi.
Osservare il riavvio in un secondo terminale:
watch -n 2 'docker ps --format "table {{.Names}} {{.Status}}" | sort'
Passaggio 4: Verificare le versioni installate.
docker version
docker compose version
Sul nostro server, l'output ha confermato:
Docker Engine: 29.0.1
Docker Compose: v2.36.0
Si noti che Docker Compose v5 è distribuito come versione 2.36.x del pacchetto docker-compose-plugin — la designazione "v5" si riferisce alla versione di riscrittura interna, non al semver del pacchetto. Questo causa qualche confusione nella documentazione; è la versione del pacchetto che l'indice apt traccia.
Verifica Post-Aggiornamento
Dieci minuti dopo l'aggiornamento, abbiamo eseguito un passaggio di verifica strutturato. Questo non è opzionale — il riavvio del daemon porterà i container attivi, ma non garantisce che i container che dipendono dagli healthcheck li abbiano effettivamente superati.
Verificare che tutti i container siano nello stato atteso.
docker ps -a --format "table {{.Names}} {{.Status}} {{.RunningFor}}" | sort
Qualsiasi container che mostra Restarting o Exited necessita di un'indagine immediata. Sul nostro server, un container — un servizio di analisi Supabase — mostrava Restarting (1). Era in un crash loop a bassa frequenza anche prima dell'aggiornamento; l'aggiornamento non lo ha causato, ma è emerso più visibilmente nella scansione post-aggiornamento. Questo è esattamente il tipo di problema preesistente che l'inventario pre-aggiornamento esiste per rilevare.
Verificare che gli healthcheck stiano passando.
docker inspect $(docker ps -q) --format '{{.Name}} health={{.State.Health.Status}}' 2>/dev/null | grep -v "health=<no value>" | sort
I container senza healthcheck espliciti mostreranno <no value> — filtrarli. Quelli che contano sono quelli da cui dipendono gli altri container tramite condition: service_healthy.
Eseguire un passaggio di validazione della configurazione compose su tutti gli stack.
for dir in /opt/*/; do
if [ -f "${dir}docker-compose.yml" ]; then
echo "--- $dir ---"
docker compose -f "${dir}docker-compose.yml" config --quiet 2>&1
fi
done
Questo rileva tutte le direttive del file compose che Compose v5 ora tratta come avvisi o errori. Sul nostro server, tre stack hanno emesso l'avviso di deprecazione della chiave version. Sono estetici ma vale la pena risolverli nel prossimo ciclo di manutenzione.
Controllare i log del daemon Docker per gli errori.
journalctl -u docker --since "1 hour ago" | grep -i -E "error|warn|fatal"
Un aggiornamento pulito non produce errori nel log del daemon. Gli avvisi sulla configurazione deprecata vale la pena annotarli ma non agire immediatamente.
Confermare la connettività di rete tra i container.
docker network ls
docker network inspect bridge --format '{{range .Containers}}{{.Name}} {{end}}'
Docker Engine 29 non ha modificato il comportamento predefinito del driver di rete, ma vale la pena confermare che le reti bridge personalizzate abbiano ancora le appartenenze ai container attese dopo il riavvio del daemon.
Cosa Abbiamo Osservato
L'aggiornamento è stato completato in meno di due minuti. Tutti i container che erano in esecuzione prima dell'aggiornamento erano in esecuzione dopo. Il cambiamento di Docker Compose v5 che abbiamo notato più immediatamente è stato un formato di output --dry-run più pulito — la nuova versione produce output strutturato e colorato che rende più facile rivedere cosa farà effettivamente un docker compose up prima di eseguirlo.
L'ordinamento dell'healthcheck depends_on si è comportato in modo identico a v2 sui nostri stack. Era atteso — avevamo validato i file compose in anticipo — ma è stato rassicurante confermarlo.
Un cambiamento sostanziale: Compose v5 non ignora più silenziosamente il campo container_name in combinazione con scale. Avevamo uno stack che usava entrambi — un vecchio residuo da prima che passassimo alle repliche appropriate. Compose v5 ha emesso un errore chiaro su docker compose up per quello stack, dove v2 aveva silenziosamente ignorato il conflitto. La correzione era una modifica di una riga per rimuovere il campo container_name ridondante.
Approfondimenti Correlati
- Distribuire Applicazioni React in Produzione: Configurazione Docker Completa con Traefik Reverse Proxy
- Costruire uno Stack di Sviluppo Multi-Tenant con Docker
- Traefik Reverse Proxy: La Guida Completa al Self-Hosting per HTTPS e Automazione SSL
- Self-Hosting di n8n su Hetzner Cloud: Tutorial Completo di Configurazione Docker