1,331 24/03/2026 07/04/2026 7 min

Dopo un deploy con Docker Compose, il container può sembrare attivo ma fallire al primo picco di traffico. I problemi più fastidiosi arrivano da rete, bind mount, limiti risorse e healthcheck scritti male.

Qui trovi una mini checklist post-deploy pensata per fare verifiche rapide e concrete. L’obiettivo è scovare errori prima che arrivino gli utenti.

Prerequisiti

Serve accesso alla macchina host e al progetto Compose. I comandi sotto assumono Docker Engine e Docker Compose v2.

  • Un file compose.yaml o docker-compose.yml già applicato.
  • Permessi per eseguire docker e leggere i log.
  • Conoscenza della porta esposta e del nome del servizio.

Note: se usi una distro con pacchetti separati, il binario può essere docker compose oppure docker-compose. I controlli restano gli stessi.

Step 1: conferma che il container sia davvero “healthy”

Un container in stato running non basta. L’healthcheck deve passare, altrimenti il servizio può restare fuori bilanciamento o dietro un reverse proxy che lo considera non pronto.

docker compose ps

docker inspect --format='{{json .State.Health}}' nome_servizio | jq

# Output:

NAME                IMAGE           COMMAND                  SERVICE        STATUS                   PORTS
app                 app:1.4.2       "/entrypoint.sh"         app            running (healthy)        0.0.0.0:8080->8080/tcp

Perché funziona: separi lo stato del processo dallo stato applicativo, che è quello che conta davvero.

Warning: se lo status è starting per troppo tempo, il test dell’healthcheck forse dipende da una risorsa non pronta, come database o cache.

Se il progetto usa un’immagine minimale, il comando jq potrebbe non esserci sull’host. Su Windows con Docker Desktop il controllo resta identico, ma il terminale cambia.

Controllo extra: retry e start period

Se l’app impiega tempo ad avviarsi, un healthcheck troppo aggressivo genera falsi negativi.

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

# Output:

(healthy)

Perché funziona: dai tempo al processo di inizializzare dipendenze e migrazioni senza segnare errori prematuri.

Step 2: verifica rete e risoluzione DNS tra container

Molti incidenti post-deploy non sono bug dell’app. Sono problemi di rete nel bridge Compose, spesso visibili solo quando il servizio parla con altri container o con un endpoint esterno.

docker network ls

docker network inspect nome_progetto_default

# Output:

[
  {
    "Name": "mioapp_default",
    "Driver": "bridge"
  }
]

Perché funziona: confermi che i servizi siano sulla stessa rete e che il DNS interno di Docker stia risolvendo i nomi servizio.

Controlla anche la risoluzione dal container:

docker compose exec app getent hosts db

docker compose exec app sh -lc 'cat /etc/resolv.conf'

# Output:

172.20.0.3      db
nameserver 127.0.0.11

Note: su immagini Alpine potresti non avere getent. In quel caso usa nslookup o busybox nslookup se presente.

Se il container usa network_mode: host, questi test cambiano. In quel caso il DNS interno di Compose non entra in gioco, e i problemi si spostano sull’host.

Edge case: porta esposta ma servizio non raggiungibile

Se il binding è corretto ma la porta risponde solo dall’host, il problema può essere nel listen address dell’app. Molte app ascoltano su 127.0.0.1 invece di 0.0.0.0.

docker compose exec app ss -lntp

# Output:

LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("app",pid=1,fd=7))

Perché funziona: il controllo mostra subito se il processo ascolta solo dentro il loopback del container.

Step 3: controlla i volumi montati e la scrivibilità reale

Un mount corretto non garantisce che i dati siano scrivibili. Dopo il deploy, il sintomo classico è il container “up” ma con errori silenziosi su cache, upload o file di lock.

docker compose exec app sh -lc 'mount | grep -E "(/data|/var/lib/app)"'

docker compose exec app sh -lc 'touch /data/.write-test && echo ok'

# Output:

ok

Perché funziona: verifichi sia il mount point sia la scrittura effettiva, non solo la presenza del path.

Se il bind mount arriva da host Linux con SELinux, la scrittura può fallire anche con permessi apparentemente corretti. In quel caso il contesto di sicurezza conta quanto il chmod.

Warning: se monti una directory vuota sull’host sopra un path dell’immagine, puoi nascondere file critici già presenti nel container. È un errore comune con configurazioni di default o asset statici.

Check rapido dei permessi dal lato host

ls -ld /srv/mioapp/data
stat -c '%U:%G %a %n' /srv/mioapp/data

# Output:

drwxr-xr-x 3 appuser appgroup 4096 /srv/mioapp/data
appuser:appgroup 755 /srv/mioapp/data

Perché funziona: puoi confrontare subito il proprietario host con l’utente con cui gira il processo nel container.

Note: su macOS e Windows i bind mount hanno semantica diversa. I permessi apparenti possono non riflettere la realtà del filesystem dell’host.

Step 4: valida limiti CPU e memoria sotto carico leggero

Molte configurazioni falliscono solo quando il servizio riceve richieste vere. I limiti CPU e RAM impostati in Compose possono uccidere il processo o creare latenza estrema se sono troppo stretti.

services:
  app:
    image: app:1.4.2
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
    mem_limit: 512m
    cpus: 0.5

# Output:

docker compose config

Perché funziona: docker compose config rende visibile la configurazione finale e smaschera errori di sintassi o chiavi ignorate.

Attenzione al dettaglio: in Compose non Swarm, il blocco deploy può essere ignorato in alcuni contesti. Per questo conviene verificare anche i limiti applicati davvero dal runtime.

docker inspect --format='{{.HostConfig.Memory}} {{.HostConfig.NanoCpus}}' $(docker compose ps -q app)

# Output:

536870912 500000000

Perché funziona: leggi i limiti effettivi impostati dal container, non solo quelli dichiarati nel file.

Mini test di pressione senza strumenti esterni

Per una verifica rapida, basta generare poche richieste consecutive e guardare i log.

for i in $(seq 1 20); do curl -fsS http://localhost:8080/health >/dev/null || break; done

docker compose logs --tail=50 app

# Output:

app  | health ok
app  | health ok

Perché funziona: una sequenza breve evidenzia subito timeout, crash o rallentamenti anomali.

Step 5: controlla ordine di avvio e dipendenze del compose

Se il servizio dipende da database o broker, il classico errore è credere che depends_on garantisca disponibilità. In realtà garantisce spesso solo l’ordine di avvio, non la prontezza applicativa.

services:
  app:
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:15
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 10

# Output:

docker compose up -d

Perché funziona: l’app parte solo quando il database risponde alla verifica di salute, non solo quando il processo è stato lanciato.

Note: alcuni ambienti usano versioni vecchie del formato Compose e non supportano tutte le condizioni. In quel caso conviene testare la readiness dentro l’entrypoint dell’app.

Verifica finale

Dopo gli step principali, fai una chiusura secca con questa sequenza. Ti dice se il deploy è sano senza aprire dashboard o pannelli grafici.

docker compose ps

docker compose logs --tail=100 --no-color

docker compose exec app sh -lc 'curl -fsS http://localhost:8080/health'

docker compose exec app sh -lc 'test -w /data && echo writable'

docker inspect --format='{{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{end}}' $(docker compose ps -q app)

# Output:

running healthy
writable

Se tutto torna, hai validato processo, rete, scrittura e readiness. È il minimo per dire che il servizio è pronto al traffico reale.

Troubleshooting

1) "container has unhealthy status"

Causa: l’endpoint di healthcheck non risponde in tempo o richiede dipendenze non ancora avviate.

Fix:

docker inspect --format='{{json .State.Health.Log}}' $(docker compose ps -q app) | jq

docker compose restart app

Se il problema persiste, aumenta start_period e verifica che il comando di health non dipenda da DNS esterno.

2) "bind: address already in use"

Causa: la porta sull’host è già occupata da un altro servizio o da una vecchia istanza rimasta attiva.

Fix:

ss -lntp | grep ':8080'

docker compose down

docker compose up -d

Se usi più stack, controlla anche se due progetti Compose pubblicano la stessa porta con nomi diversi.

3) "permission denied"

Causa: il container scrive su un bind mount non allineato con UID, GID o contesto di sicurezza dell’host.

Fix:

id
ls -ld /srv/mioapp/data
sudo chown -R 1000:1000 /srv/mioapp/data

Se sei su una distro con SELinux, prova anche a rimontare con etichetta corretta nel compose o a verificare i log di audit.

Conclusione

La checklist giusta dopo il deploy non è lunga. Deve però toccare i punti che rompono davvero il servizio: salute, rete, volumi e limiti risorse.

Il prossimo passo utile è automatizzare questi controlli in uno script di post-deploy, da eseguire subito dopo docker compose up -d. Così intercetti regressioni prima del traffico reale.