Introduzione a Docker nel Contesto Web
Uno dei problemi più frustranti per ogni sviluppatore web è l'incoerenza degli ambienti. Avrai sicuramente vissuto la situazione in cui un progetto funziona perfettamente sulla tua macchina locale, ma crasha non appena viene caricato sul server di staging o in produzione. Questo fenomeno, noto come "it works on my machine", è solitamente causato da differenze nelle versioni di PHP, Node.js, Python o dalle configurazioni divergenti del database e del sistema operativo.
Docker risolve questo problema introducendo il concetto di containerizzazione. A differenza delle macchine virtuali (VM), che emulano un intero hardware e richiedono un sistema operativo guest completo, i container Docker condividono il kernel del sistema operativo host. Questo li rende incredibilmente leggeri, veloci da avviare e facili da distribuire.
Per uno sviluppatore web, Docker significa poter definire l'intera infrastruttura dell'applicazione (web server, database, cache, runtime) in un file di configurazione versionabile tramite Git. In questo modo, ogni membro del team lavora esattamente nello stesso ambiente, indipendentemente dal fatto che utilizzi Windows, macOS o Linux.
Concetti Fondamentali: Immagini, Container e Volumi
Prima di scrivere codice, è fondamentale comprendere i tre pilastri di Docker.
1. Docker Image
L'immagine è un template read-only che contiene le istruzioni per creare un container. Immaginala come una "istantanea" (snapshot) del sistema: include il sistema operativo minimale, le dipendenze, le variabili d'ambiente e il codice dell'applicazione. Le immagini sono costruite partendo da un file chiamato Dockerfile.
2. Docker Container
Il container è l'istanza eseguibile di un'immagine. Se l'immagine è la classe in programmazione a oggetti, il container è l'oggetto. Puoi avviare più container basati sulla stessa immagine, ognuno isolato dagli altri.
3. Docker Volumes
Per impostazione predefinita, i dati scritti all'interno di un container sono volatili: se il container viene eliminato, i dati spariscono. I volumi permettono di mappare una cartella della macchina host a una cartella all'interno del container. Questo è fondamentale per due motivi: mantenere i dati del database persistenti e permettere lo sviluppo in tempo reale (hot-reloading) mappando il codice sorgente locale nel container.
Creare il proprio ambiente con Dockerfile
Il Dockerfile è il cuore della personalizzazione. È un file di testo che elenca i comandi necessari per assemblare l'immagine.
Vediamo un esempio pratico per un'applicazione Node.js. Supponiamo di avere un progetto con un server Express.
# 1. Partiamo da un'immagine ufficiale di Node.js (versione LTS)
FROM node:18-alpine
# 2. Definiamo la directory di lavoro all'interno del container
WORKDIR /app
# 3. Copiamo i file dei pacchetti per sfruttare la cache di Docker
COPY package*.json ./
# 4. Installiamo le dipendenze
RUN npm install
# 5. Copiamo il resto del codice sorgente
COPY . .
# 6. Esponiamo la porta su cui gira l'app
EXPOSE 3000
# 7. Comando per avviare l'applicazione
CMD ["npm", "start"]
Analisi del codice
- FROM node:18-alpine: Usiamo l'immagine
alpine, una distribuzione Linux ultra-leggera che riduce drasticamente la dimensione finale dell'immagine. - WORKDIR /app: Crea una cartella dove avverranno tutte le operazioni successive.
- L'ordine di COPY: Copiamo prima solo i file
package.json. Docker crea dei layer; se non modifichiamo le dipendenze, Docker riutilizzerà il layer dell'installazione dei pacchetti (npm install) senza rieseguirlo a ogni modifica del codice, velocizzando enormemente i tempi di build.
Orchestrazione con Docker Compose
Raramente un'app web consiste in un solo servizio. Solitamente abbiamo un frontend, un backend e un database (es. MySQL o MongoDB). Gestire ogni container singolarmente tramite riga di comando diventerebbe proibitivo. Qui entra in gioco Docker Compose.
Docker Compose permette di definire un intero stack di servizi in un unico file docker-compose.yml.
Ecco un esempio di configurazione per un'app PHP con Laravel e MySQL:
version: '3.8'
services:
# Servizio per il Web Server (Nginx)
webserver:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./:/var/www/html
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
# Servizio per l'applicazione PHP (Laravel)
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/var/www/html
environment:
- DB_HOST=db
- DB_DATABASE=laravel_db
# Servizio per il Database (MySQL)
db:
image: mysql:8.0
restart: always
environment:
MYSQL_DATABASE: laravel_db
MYSQL_ROOT_PASSWORD: root_password
volumes:
- dbdata:/var/lib/mysql
volumes:
dbdata:
Spiegazione della configurazione
- depends_on: Indica a Docker di avviare prima il database e l'app prima di avviare il server Nginx.
- ports: Mappa la porta 80 dell'host alla porta 80 del container. Ora l'app è accessibile via
http://localhost. - environment: Passiamo le variabili di configurazione direttamente al container, evitando di scrivere password in chiaro nei file di codice.
- dbdata: Definiamo un volume nominato per assicurarci che, anche se spegniamo i container, i nostri dati MySQL rimangano salvati sul disco dell'host.
Esempi Pratici e Casi d'Uso Reali
Caso 1: Gestione di versioni multiple di runtime
Immagina di dover mantenere due progetti: uno vecchio in PHP 7.4 e uno nuovo in PHP 8.2. Installare entrambe le versioni globalmente sul PC può creare conflitti di percorso e configurazioni. Con Docker, crei due file docker-compose.yml diversi: uno che richiama l'immagine php:7.4-fpm e uno che richiama php:8.2-fpm. Passi da un progetto all'altro con un semplice comando docker-compose up -d senza sporcare il tuo sistema operativo.
Caso 2: Onboarding rapido di nuovi sviluppatori
Senza Docker, un nuovo sviluppatore deve: installare il database, configurare l'estensione Xdebug, installare Node.js, configurare il server web e creare i database. Questo processo può richiedere ore. Con Docker, il nuovo collega clona la repository e digita docker-compose up. In pochi minuti, l'intero ambiente è attivo e identico a quello del resto del team.
Caso 3: Test di integrazione isolati
Puoi utilizzare Docker per avviare un database temporaneo durante i test automatizzati (CI/CD). Invece di usare un database di test condiviso che potrebbe essere corrotto da altri test, Docker avvia un container pulito, esegue i test e poi distrugge il container, garantendo che ogni test parta da uno stato deterministico.
Errori Comuni e FAQ
Perché il mio container è lentissimo su macOS o Windows?
I container Docker girano nativamente su Linux. Su Windows e Mac, Docker crea una piccola VM Linux invisibile. Il collo di bottiglia principale è spesso l'I/O del disco durante il mapping dei volumi.
Soluzione: Su macOS, usa le "VirtioFS" nelle impostazioni di Docker Desktop. In generale, evita di mappare migliaia di piccoli file (come la cartella node_modules) tra host e container; è meglio installarli direttamente dentro l'immagine o usare volumi anonimi.
Come posso eseguire comandi dentro un container attivo?
Se hai bisogno di eseguire una migrazione del database o installare un pacchetto mentre l'app è in funzione, usa il comando exec:
docker exec -it nome_container bash (o sh per immagini alpine).
L'immagine è troppo grande, cosa posso fare?
L'uso di immagini basate su Ubuntu o Debian può portare a immagini di 1GB. Usa le versioni -alpine o -slim per ridurre le dimensioni a poche decine di megabyte.
Prossimi Passi e Approfondimenti
Ora che hai familiarità con Docker e Docker Compose, puoi esplorare concetti più avanzati per scalare le tue applicazioni:
- Multi-stage Builds: Permettono di usare un'immagine per compilare il codice (es. compilazione di TypeScript o assets Webpack) e poi copiare solo i file finali in un'immagine di produzione molto più leggera, eliminando i compilatori e le dipendenze di sviluppo.
- Kubernetes (K8s): Se l'applicazione cresce e hai bisogno di gestire decine di container distribuiti su più server, Kubernetes è lo standard per l'orchestrazione, l'auto-scaling e l'auto-healing.
- CI/CD Pipelines: Integra Docker in GitHub Actions o GitLab CI per automatizzare la creazione dell'immagine e il deployment verso cloud provider come AWS, Google Cloud o DigitalOcean.
- Docker Hub: Impara a creare repository private per le tue immagini, facilitando il passaggio tra l'ambiente di sviluppo e quello di produzione.
Docker non è solo uno strumento di deployment, ma un cambio di paradigma che sposta l'attenzione dall'installazione del software alla definizione dell'infrastruttura come codice (Infrastructure as Code).