
Per anni ho lottato con ambienti che funzionavano in locale ma si rompevano in produzione: dipendenze diverse, configurazioni manuali, sorprese continue. Le virtual machine aiutano, ma sono pesanti, lente da gestire e da trasferire.
Il caso più classico: un applicativo che funziona solo sulla macchina di uno sviluppatore. Nessun altro riusciva a farlo partire. Il motivo? Una variabile d'ambiente impostata nel suo .bashrc tre anni prima, durante un test, e mai documentata. Quando è andato in ferie, il deploy si è fermato. Letteralmente. Nessuno sapeva cosa mancasse, e lui non se lo ricordava nemmeno.
Situazioni come questa non sono eccezioni. Sono la norma in ambienti dove l'infrastruttura vive nella testa delle persone invece che nel codice.
Docker ha risolto tutto: container leggeri, isolati e riproducibili. Basta un file di configurazione e ricrei l'ambiente identico ovunque.
Esempio pratico con NGINX: un web server consolidato e affidabile per proxy, siti statici o altro. Invece di installarlo a mano su ogni macchina, lo containerizzo.
Cosa ho fatto:
- Dockerfile semplice per buildare l'immagine.
- Configurazione NGINX con una pagina HTML custom.
Il repo completo è qui: gitlab.com/MiPnamic/approccio-modulare, cartella 000-nginx-e-docker. Clonala e prova.
Comandi per partire:
- Vai nella cartella.
- Build: docker build -t nginx-orlandin .
- Run: docker run -dp 8000:80 nginx-orlandin
- Apri http://localhost:8000, NGINX customizzato pronto.
Niente più dipendenze dal sistema host. Tutto versionato con Git.
Risultato: setup in minuti, zero sorprese, facile da scalare.
Dopo il primo container
Un singolo Dockerfile è il punto di partenza, non la destinazione. Nella pratica, il passo successivo è quasi sempre un docker-compose.yml che mette insieme più servizi: il web server, il database, magari un key-value store per le sessioni. Ogni componente isolato, ogni componente descritto.
Il vantaggio reale non è la velocità del primo setup. È che chiunque, in qualsiasi momento, può clonare il repo, lanciare un comando e avere l'ambiente completo. Nessuna guida di 40 pagine. Nessuna telefonata al collega che "l'aveva fatto funzionare l'ultima volta."
Con i multi-stage build si va oltre: un'unica pipeline che compila, testa e produce l'immagine finale. In pratica, il Dockerfile si divide in due (o più) stage. Il primo parte da un'immagine pesante, con compilatore, package manager e tutte le dipendenze di build. Installa i moduli, compila il codice, esegue i test. Il secondo stage parte da un'immagine minimale (alpine, distroless, o anche scratch) e copia solo gli artefatti prodotti dal primo: il binario compilato, i file statici, la configurazione. Tutto il resto resta fuori.
La differenza è concreta. Un'immagine Node.js con l'intero ambiente di build pesa facilmente 1.2 GB. La stessa applicazione, con un multi-stage che copia solo il build finale su un'immagine nginx alpine, scende a 40-50 MB. Non è solo una questione di spazio disco: meno componenti significa meno vulnerabilità note, meno aggiornamenti da gestire, meno cose che possono rompersi. In produzione, ogni pacchetto che non serve è un rischio che non dovresti correre.
Il pattern funziona per qualsiasi stack. Un progetto Go compila in un binario statico: lo copi su un'immagine scratch e hai un container che contiene letteralmente solo il tuo eseguibile. Un progetto Java produce un JAR: lo copi su un'immagine con solo la JRE, senza JDK, senza Maven, senza Gradle. Il principio è sempre lo stesso: separa chi costruisce da chi esegue.
Cosa Docker non risolve
Docker isola e rende trasportabile, ma non mette ordine. Se la tua infrastruttura è un insieme di container lanciati a mano, con comandi copia-incollati da un file di testo, hai spostato il problema senza risolverlo. Il caos è in un formato diverso, ma resta caos.
Lo vedo continuamente. Un team con 15 file docker-compose sparsi su 3 repository diversi, nessuno dei quali aggiornato. Il compose del repo principale usa un'immagine Postgres 13, quello del servizio di notifiche una Postgres 15, il terzo non specifica nemmeno la versione. Nessuno sa quale sia quello "giusto" e nessuno osa toccarli perché "funzionano, più o meno." Un altro team che fa deploy copiando comandi docker run da un messaggio fissato in un canale Slack. Venti righe di flag, volumi e variabili d'ambiente. Qualcuno a un certo punto ne ha modificato uno, il messaggio originale è rimasto lo stesso, e nessuno sa più quale versione sia in produzione.
Il pattern è sempre quello: Docker risolve l'isolamento, non l'organizzazione. Puoi avere container perfettamente funzionanti e un'infrastruttura completamente illeggibile. Senza convenzioni chiare, senza un unico punto di verità per la configurazione, containerizzare peggiora le cose perché dà l'illusione di avere tutto sotto controllo.
Per passare da "container che funzionano" a "infrastruttura che si descrive da sola", serve uno strato sopra: Terraform, OpenTofu, o un tool equivalente. Docker è il mattone. L'Infrastructure as Code è il progetto dell'edificio.
L'errore che vedo spesso: team che containerizzano tutto e pensano di aver risolto il debito tecnico. In realtà hanno solo reso il debito più portabile.
Podman: l'alternativa che preferisco
Nota aggiornata (2025): Questo contenuto è del 2022, quando Docker era lo standard de facto. Oggi, per nuovi setup o migrazioni, preferisco Podman: rootless di default, compatibile con Dockerfile esistenti, e pienamente open-source senza sorprese di licenza.
Ho migrato tre ambienti di un cliente da Docker a Podman in meno di una settimana. Il motivo iniziale era la licenza (Docker Desktop a pagamento sopra i 250 dipendenti), ma il risultato è stato migliore del previsto: i container girano senza privilegi di root, il team non ha dovuto cambiare un singolo Dockerfile, e il CI/CD si è adattato modificando due righe di configurazione.
Cosa ha funzionato subito: i Dockerfile, senza eccezioni. Podman li legge e li esegue nello stesso modo. I volumi con path assoluti, nessun problema. Le immagini dal registry, identiche. Il build context, identico. Se il tuo workflow è "build + run", la migrazione è trasparente.
Cosa ha richiesto attenzione: docker-compose. Podman non include un equivalente nativo di Compose nella stessa installazione. Puoi usare podman-compose (un progetto Python separato) oppure la versione standalone di docker-compose che parla con il socket di Podman. Nel nostro caso, podman-compose ha gestito correttamente il 90% dei file compose esistenti. I problemi sono arrivati con le network personalizzate: Docker crea automaticamente una bridge network per ogni compose, Podman in modalità rootless usa slirp4netns e il comportamento del DNS tra container cambia. In pratica, un servizio che si collegava a un altro usando il nome del servizio come hostname ha smesso di risolvere. La soluzione: definire esplicitamente la network nel compose e usare podman network create prima del primo avvio.
L'altro intoppo: alcuni script interni che chiamavano docker direttamente. Un alias (alias docker=podman) e passa la paura. Però se hai script che interagiscono con il Docker socket (/var/run/docker.sock), quelli vanno aggiornati per puntare al socket di Podman, che in modalità rootless vive in un path utente diverso.
Se stai partendo ora o vuoi migrare, Podman elimina rischi di sicurezza e vendor lock-in. L'ho fatto per diversi clienti: funziona allo stesso modo, spesso meglio.
Se hai ambienti da containerizzare, o stai valutando la migrazione da Docker... Parliamone. Ti guido passo per passo, senza drammi.

In sintesi (TL;DR)
- Il classico 'sul mio PC funziona' nasce da ambienti non riproducibili: variabili d'ambiente dimenticate, configurazioni manuali, dipendenze invisibili.
- Docker risolve l'isolamento e la portabilità, ma non mette ordine: containerizzare il caos produce caos portabile.
- Multi-stage build per immagini di produzione minimali. Docker Compose per orchestrare più servizi in locale.
- Per nuovi setup preferisco Podman: rootless di default, compatibile con Dockerfile esistenti, nessun problema di licenza.