1,712 25/03/2026 07/04/2026 9 min

Introduzione

Il caso più fastidioso non è il container che crasha subito. È quello che risulta healthy, espone la porta, ma l’applicazione non serve traffico o perde dati dopo un restart.

Succede spesso con Docker Compose quando si combinano bind mount, limiti di memoria stretti, healthcheck troppo ottimistici e una rete interna configurata male. Il sintomo inganna. La dashboard dice verde. Gli utenti vedono errori, timeout o contenuti vecchi.

In questo articolo lavoriamo sullo scenario peggiore: cosa fare quando va storto. Prima diagnosi, poi rollback, poi ripristino. L’obiettivo è evitare il classico deploy che “sembra riuscito” e lascia il servizio in uno stato ambiguo.

Prerequisiti

  • Docker Engine recente e Docker Compose v2.
  • Accesso alla macchina host con privilegi sudo.
  • Un file compose.yaml versionato in Git.
  • Log dell’applicazione e del container disponibili.
  • Un backup o almeno uno snapshot del volume dati.

Note: i comandi sotto usano esempi generici. Adatta nomi di servizio, percorsi e porte al tuo stack reale.

Warning: se il volume contiene dati critici, non cancellarlo prima di aver verificato che il ripristino sia possibile. Il problema più comune è fare pulizia troppo presto.

Step 1: capire se il guasto è nel processo, nella rete o nel volume

Il primo errore è guardare solo lo stato del container. Un container può essere running e non funzionare per tre motivi diversi: il processo applicativo è bloccato, la rete interna non risolve il servizio, oppure il volume montato ha dati incoerenti.

Parti da tre controlli in parallelo. Stato, log e mount. Non saltare nessuno dei tre.

docker compose ps

docker compose logs --tail=100 web

docker inspect web --format '{{json .Mounts}}' | jq

# Output:

web deve risultare Up. Nei log devi vedere l’avvio completo o un errore preciso. Nei mount devi verificare che il percorso host punti al volume giusto.

Se il log mostra messaggi come permission denied o read-only file system, il problema è quasi sempre nel mount. Se vedi connection refused verso database o cache, il problema è nella rete o nel servizio dipendente.

Step 2: verificare la rete Compose quando il DNS interno mente

Molti stack Compose falliscono dopo un riavvio perché il servizio usa un nome host interno cambiato o perché la rete è stata ricreata. Il container risulta sano, ma l’app non raggiunge il database o il broker.

La diagnosi migliore è entrare nel container e risolvere il nome dal suo namespace di rete. Non fidarti della risoluzione dall’host.

docker compose exec web sh -lc 'getent hosts db && nc -zv db 5432'

# Output:

Devi vedere un IP interno della rete Compose e una connessione riuscita sulla porta del servizio.

Se il nome non si risolve, controlla che entrambi i servizi siano sulla stessa rete. Se usi reti multiple, il servizio potrebbe essere collegato a quella sbagliata.

services:
  web:
    networks:
      - front
      - back
  db:
    networks:
      - back

networks:
  front:
  back:

Note: se il problema compare dopo down e up, verifica che nessun script esterno si aspetti un IP fisso. Con Compose, il nome servizio è la regola corretta. L’IP non lo è.

Step 3: controllare i volumi quando i dati “spariscono” dopo il restart

Il sintomo classico è questo: l’app parte, ma sembra vuota. Utenti disconnessi, cache azzerata, file mancanti, configurazione resettata. Spesso il volume non è sparito. È stato montato nel posto sbagliato.

Con un bind mount basta un errore di percorso per creare una directory nuova e vuota sull’host. Il container la usa senza protestare. Il risultato è una perdita apparente di dati.

docker inspect web --format '{{range .Mounts}}{{println .Type .Source "->" .Destination}}{{end}}'
ls -lah /srv/app/data

# Output:

Il percorso host deve contenere i file attesi. Se trovi directory appena create, probabilmente stai montando il path sbagliato o l’utente del container non ha permessi coerenti.

Per un ripristino rapido, ferma il servizio e rimonta il volume corretto nel compose. Poi riavvia solo il servizio interessato, non l’intero stack.

services:
  web:
    volumes:
      - type: bind
        source: /srv/app/data
        target: /var/lib/app
        consistency: delegated

Warning: non usare un bind mount temporaneo “di emergenza” senza documentarlo. È un classico rollback incompleto che torna a colpire al prossimo deploy.

Step 4: leggere i limiti di risorse prima che il kernel tagli il processo

Un container con memoria troppo stretta può passare il healthcheck e morire pochi minuti dopo. Oppure restare vivo ma degradato. L’errore più comune è interpretare exit code 137 come bug applicativo. Spesso è il kernel che ha ucciso il processo per OOM.

Verifica i limiti effettivi, non solo quelli dichiarati nel file Compose.

docker inspect web --format 'Memory={{.HostConfig.Memory}} CpuQuota={{.HostConfig.CpuQuota}} PidsLimit={{.HostConfig.PidsLimit}}'
docker stats --no-stream web
journalctl -k --since '30 min ago' | grep -i oom

# Output:

Devi vedere i limiti impostati e, se c’è stato un OOM, una riga del kernel che conferma l’uccisione del processo.

Se il servizio usa PHP-FPM, Node.js o Java, il problema emerge spesso sotto carico e non in avvio. Il healthcheck resta verde perché controlla una pagina leggera o un endpoint cache-friendly.

services:
  web:
    mem_limit: 512m
    cpus: 1.00
    pids_limit: 200

Note: non alzare subito tutti i limiti. Prima misura il picco reale con docker stats e con i log dell’app. Un aumento cieco nasconde il problema.

Step 5: rendere il healthcheck davvero utile

Il healthcheck deve misurare il servizio, non solo il processo. Un curl localhost che riceve una pagina statica non basta se il backend non è raggiungibile o se il database è in errore.

Un buon healthcheck prova il percorso critico dell’app. Per esempio, un endpoint che verifica DB, cache e scrittura minima su disco.

services:
  web:
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://localhost:8080/healthz || exit 1"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 30s

# Output:

Il container deve passare a healthy solo quando il servizio risponde davvero. Se l’endpoint è troppo leggero, il risultato è un falso positivo.

Se il problema compare dopo il deploy, confronta il vecchio e il nuovo endpoint. A volte il nuovo codice non espone più la stessa verifica, ma Compose continua a considerarla valida.

Quando il healthcheck crea un loop

Un healthcheck troppo aggressivo può uccidere un container ancora in warm-up. Se l’app impiega 40 secondi ad aprire il pool DB, ma il start_period è di 10 secondi, avrai un falso fallimento ad ogni riavvio.

In quel caso aumenta il start_period e riduci la frequenza delle prove. La regola è semplice: il check non deve competere con l’avvio.

Step 6: fare rollback senza distruggere i dati

Quando il deploy rompe l’ambiente, la priorità è tornare a una versione funzionante senza toccare il volume dati. Il rollback corretto riguarda immagine, configurazione e rete. Non il contenuto persistente, salvo corruzione verificata.

Se usi Git, conserva sempre una release precedente pronta. Con Compose, il rollback più pulito è ripristinare il file e rilanciare solo i servizi interessati.

git checkout HEAD~1 -- compose.yaml

docker compose pull web

docker compose up -d --no-deps web

# Output:

Il servizio deve ripartire con la configurazione precedente. I dati nel volume restano intatti.

Se hai già aggiornato un volume schema-sensitive, fai attenzione. Tornare a un’immagine vecchia con dati nuovi può creare incompatibilità. In quel caso serve un dump, non solo un rollback.

Note: se il deploy è stato fatto con tag latest, il rollback è più fragile. Meglio versioni immutabili, come app:2026.03.25-1.

Step 7: ripristinare i dati quando il volume è corrotto

La corruzione vera è meno frequente del mount sbagliato, ma va trattata subito. Se il database embedded, la cache persistente o una directory di upload mostrano file incoerenti, fermati e copia il volume prima di tutto.

Lavora su una copia, non sull’originale.

docker compose stop web
rsync -aHAX /srv/app/data/ /srv/app/data.bak/

# Output:

Devi ottenere una copia completa, pronta per un tentativo di ripristino o per un confronto file per file.

Se il servizio è un database, usa lo strumento nativo di recovery. Se è una directory di file, confronta checksum e timestamp. Se è una cache, spesso conviene svuotarla e ricostruirla, non ripararla.

Verifica finale

Dopo il rollback o il ripristino, esegui sempre la stessa sequenza. Non affidarti alla sola dashboard.

  • Il container deve risultare Up e healthy.
  • La rete interna deve risolvere i servizi dipendenti.
  • I mount devono puntare al path corretto.
  • I limiti risorse devono essere coerenti con il carico reale.
  • I log non devono mostrare retry infiniti o errori di permesso.
docker compose ps

docker compose exec web sh -lc 'curl -fsS http://localhost:8080/healthz && getent hosts db'

docker stats --no-stream web

# Output:

Lo stato deve essere stabile per almeno alcuni minuti. Se il servizio degrada subito dopo il check, il problema è ancora aperto.

Troubleshooting

Errore: service "db" is unhealthy

Cause: il healthcheck dipende da un servizio upstream non ancora pronto o bloccato.

Fix: aumenta start_period e verifica il servizio dipendente con un check diretto.

docker compose exec db sh -lc 'pg_isready -U app'

# Output:

accepting connections

Errore: permission denied while trying to connect to the Docker daemon socket

Cause: l’utente corrente non ha permessi sul socket Docker.

Fix: usa sudo oppure aggiungi l’utente al gruppo docker e riapri la sessione.

sudo usermod -aG docker $USER
newgrp docker

# Output:

Il comando docker ps deve funzionare senza sudo nella nuova sessione.

Errore: bind: address already in use

Cause: la porta host è già occupata da un vecchio container, da un processo locale o da un rollback incompleto.

Fix: trova il processo, libera la porta e rilancia solo il servizio necessario.

ss -ltnp | grep ':8080'
docker compose down

# Output:

La porta deve risultare libera prima del nuovo up -d.

Conclusione

Quando un Compose stack va storto, il problema raramente è solo “Docker”. Di solito c’è una combinazione di rete, volume e limiti risorse, con un healthcheck troppo fiducioso.

La disciplina giusta è sempre la stessa: diagnosticare con dati reali, fare rollback senza toccare i dati persistenti e ripristinare solo dopo aver capito la causa.

Prossimo passo concreto: prendi il tuo compose.yaml, aggiungi un healthcheck che tocchi una dipendenza reale e prepara un rollback con tag versionati, non con latest.