Introduzione: perchè Docker per il Frontend?
Quante volte hai sentito la frase "ma sul mio computer funziona"? Docker risolve questo problema alla radice: ogni ambiente, dallo sviluppo alla produzione, esegue esattamente lo stesso container con le stesse dipendenze, la stessa versione di Node.js e la stessa configurazione di sistema.
Per gli sviluppatori frontend, Docker non e solo uno strumento per il backend. Containerizzare un'applicazione Angular significa avere build riproducibili al 100%, ambienti di sviluppo consistenti tra i membri del team, e un processo di deploy semplificato. In questo articolo costruiremo un Dockerfile multi-stage ottimizzato, configureremo nginx per il routing SPA, e creeremo un ambiente di sviluppo completo con Docker Compose.
Cosa Imparerai in Questo Articolo
- perchè Docker e utile per i progetti frontend
- Come costruire un Dockerfile multi-stage per Angular
- Come configurare
.dockerignoreper build più veloci - Come usare Docker Compose per lo sviluppo locale
- Come gestire le variabili d'ambiente al build time
- Come configurare nginx per il routing SPA
- Ottimizzazione del layer caching di Docker
- Health check e debugging dei container
1. Concetti Base di Docker per Frontend
Prima di scrivere il nostro Dockerfile, e importante comprendere tre concetti chiave di Docker nel contesto di un'applicazione frontend:
Docker nel Contesto Frontend
| Concetto | Descrizione | Uso Frontend |
|---|---|---|
| Image | Un template immutabile che contiene tutto il necessario per eseguire l'applicazione | L'app Angular compilata + nginx |
| Container | Un'istanza in esecuzione di un'immagine | Il server nginx che serve i file statici |
| Multi-stage build | Un Dockerfile con più fasi, dove solo il risultato finale viene incluso nell'immagine | Fase 1: build con Node.js, Fase 2: serve con nginx |
Il vantaggio del multi-stage build e che l'immagine finale non contiene Node.js, npm, o i
node_modules: solo i file statici compilati e nginx. Questo riduce drasticamente
la dimensione dell'immagine da circa 1.5 GB a meno di 50 MB.
2. Il File .dockerignore
Prima di creare il Dockerfile, configuriamo il .dockerignore. Questo file funziona
come .gitignore ma per il contesto di build Docker: tutto ciò che viene escluso
non viene inviato al Docker daemon, velocizzando enormemente la build.
# .dockerignore
node_modules
dist
.angular
.git
.github
.vscode
.idea
# File di configurazione non necessari per la build
*.md
LICENSE
.editorconfig
.prettierrc
.eslintrc*
# File Docker (evita ricorsione)
Dockerfile*
docker-compose*
.dockerignore
# File di test
coverage
e2e
*.spec.ts
# File di ambiente locali
.env.local
.env.development.local
Non Dimenticare node_modules!
Escludere node_modules dal contesto di build e cruciale: senza questa esclusione,
Docker inviera centinaia di megabyte di file al daemon ad ogni build, anche se il Dockerfile
esegue npm ci per installare le dipendenze da zero. La build può passare da
2 minuti a 30 secondi semplicemente aggiungendo questa riga.
3. Dockerfile Multi-Stage per Angular
Ecco il Dockerfile multi-stage ottimizzato per un'applicazione Angular. La prima fase esegue la build, la seconda serve i file statici con nginx:
# ============================================
# Stage 1: Build dell'applicazione Angular
# ============================================
FROM node:20-alpine AS build
# Impostare la directory di lavoro
WORKDIR /app
# Copiare PRIMA i file di dipendenze (per il layer caching)
COPY package.json package-lock.json ./
# Installare le dipendenze
RUN npm ci --ignore-scripts
# Copiare il resto del codice sorgente
COPY . .
# Eseguire la build di produzione
RUN npx ng build --configuration=production
# ============================================
# Stage 2: Servire con nginx
# ============================================
FROM nginx:1.25-alpine AS production
# Rimuovere la configurazione di default di nginx
RUN rm /etc/nginx/conf.d/default.conf
# Copiare la nostra configurazione nginx
COPY nginx.conf /etc/nginx/conf.d/
# Copiare i file compilati dallo stage di build
COPY --from=build /app/dist/my-app/browser /usr/share/nginx/html
# Esporre la porta 80
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
# Avviare nginx in foreground
CMD ["nginx", "-g", "daemon off;"]
Osserva l'ordine delle istruzioni COPY: prima copiamo package.json e
package-lock.json, poi eseguiamo npm ci, e solo dopo copiamo il codice
sorgente. Questo ordine e fondamentale per il layer caching di Docker.
perchè l'Ordine dei COPY e Importante
Docker costruisce le immagini a strati (layer). Ogni istruzione crea un nuovo layer, e Docker
riutilizza i layer dalla cache quando i file di input non sono cambiati. Se copiamo tutto
insieme con un singolo COPY . ., qualsiasi modifica a qualsiasi file invalida la
cache dell'installazione delle dipendenze. Con l'ordine corretto:
- Modifica al codice sorgente: solo gli ultimi 2 layer vengono ricostruiti
- Modifica a
package.json: vengono ricostruiti danpm ciin poi - Nessuna modifica: build quasi istantanea dalla cache
4. Configurazione Nginx per SPA
Le Single Page Application come Angular gestiscono il routing lato client. Questo significa che
nginx deve servire index.html per qualsiasi percorso che non corrisponde a un file
statico. Ecco la configurazione:
# nginx.conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Compressione gzip
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml;
# Cache per i file statici con hash nel nome
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# SPA fallback: qualsiasi percorso non trovato viene servito da index.html
location / {
try_files $uri $uri/ /index.html;
}
# Header di sicurezza
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Disabilitare il listing delle directory
autoindex off;
# Nascondere la versione di nginx
server_tokens off;
}
Punti Chiave della Configurazione Nginx
try_files $uri $uri/ /index.html: la regola fondamentale per il routing SPAgzip: compressione per ridurre la dimensione dei file trasferitiexpires 1y: cache aggressiva per i file con hash (Angular usa hashing automatico)- Header di sicurezza: protezione base contro XSS e clickjacking
5. Variabili d'Ambiente al Build Time
Un problema comune nelle applicazioni Angular containerizzate e la gestione delle variabili d'ambiente. Angular compila le variabili nel bundle al momento della build, quindi dobbiamo passarle come build arguments:
# Dockerfile con build arguments
FROM node:20-alpine AS build
WORKDIR /app
# Definire gli argomenti di build
ARG API_URL=http://localhost:3000
ARG ENVIRONMENT=production
ARG GOOGLE_ANALYTICS_ID
# Impostare le variabili d'ambiente per la build
ENV API_URL=${API_URL}
ENV ENVIRONMENT=${ENVIRONMENT}
ENV GOOGLE_ANALYTICS_ID=${GOOGLE_ANALYTICS_ID}
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
COPY . .
# Usare la configurazione corrispondente all'ambiente
RUN npx ng build --configuration=${ENVIRONMENT}
FROM nginx:1.25-alpine AS production
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/
COPY --from=build /app/dist/my-app/browser /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Per passare i build arguments durante la build:
# Build con variabili personalizzate
docker build \
--build-arg API_URL=https://api.example.com \
--build-arg ENVIRONMENT=production \
--build-arg GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX \
-t my-angular-app:latest .
# Build per staging
docker build \
--build-arg API_URL=https://staging-api.example.com \
--build-arg ENVIRONMENT=staging \
-t my-angular-app:staging .
6. Docker Compose per lo Sviluppo
Per lo sviluppo locale, Docker Compose permette di orchestrare più servizi con un singolo comando. Ecco una configurazione che include l'app Angular con hot-reload, un backend API e un database:
# docker-compose.yml
services:
angular-app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "4200:4200"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
command: npx ng serve --host 0.0.0.0
api:
image: node:20-alpine
working_dir: /app
ports:
- "3000:3000"
volumes:
- ./api:/app
command: npm run dev
environment:
- DATABASE_URL=postgresql://user:password@db:5432/mydb
db:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Dockerfile per lo Sviluppo
Il Dockerfile di sviluppo e diverso da quello di produzione: include Node.js, monta il codice
sorgente come volume, e usa ng serve con hot-reload:
# Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
# Installare Angular CLI globalmente
RUN npm install -g @angular/cli
# Copiare i file di dipendenze
COPY package.json package-lock.json ./
# Installare le dipendenze
RUN npm ci
# La directory di lavoro viene montata come volume
# quindi non serve COPY del codice sorgente
EXPOSE 4200
CMD ["npx", "ng", "serve", "--host", "0.0.0.0", "--poll", "2000"]
Comandi Docker Compose Essenziali
| Comando | Descrizione |
|---|---|
docker compose up -d |
Avvia tutti i servizi in background |
docker compose down |
Ferma e rimuove i container |
docker compose logs -f angular-app |
Visualizza i log in tempo reale |
docker compose exec angular-app sh |
Apre una shell nel container |
docker compose build --no-cache |
Ricostruisce senza cache |
7. Ottimizzazione della Build Docker
Ottimizzare la build Docker non e solo una questione di velocità: immagini più piccole significano deploy più rapidi, minor consumo di banda e una superficie di attacco ridotta.
Confronto Dimensioni Immagini
# Confronto dimensioni con diverse immagini base
# node:20 (Debian) ~1.1 GB
# node:20-slim ~240 MB
# node:20-alpine ~130 MB
# nginx:alpine ~40 MB (solo per la fase finale)
# Dimensioni tipiche dell'immagine finale:
# Senza multi-stage: ~1.5 GB (Node.js + node_modules + dist)
# Con multi-stage: ~45 MB (nginx + file statici)
Strategie di Ottimizzazione
# Dockerfile ottimizzato con tutte le best practice
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
# Installare solo le dipendenze di produzione per ridurre i layer
RUN npm ci --ignore-scripts && npm cache clean --force
FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npx ng build --configuration=production
# Rimuovere i source map se non necessari
RUN find /app/dist -name "*.map" -delete
FROM nginx:1.25-alpine AS production
# Usare un utente non-root per sicurezza
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/my-app/browser /usr/share/nginx/html
RUN chown -R appuser:appgroup /usr/share/nginx/html && \
chown -R appuser:appgroup /var/cache/nginx && \
chown -R appuser:appgroup /var/log/nginx && \
touch /var/run/nginx.pid && \
chown appuser:appgroup /var/run/nginx.pid
USER appuser
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
8. Health Check e Debugging
Gli health check permettono a Docker (e agli orchestratori come Kubernetes) di verificare che il container sia effettivamente funzionante, non solo in esecuzione.
# Health check nel Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
Comandi Utili per il Debugging
# Verificare lo stato di salute del container
docker inspect --format='{{.State.Health.Status}}' my-angular-container
# Visualizzare i log del container
docker logs my-angular-container --tail 50
# Entrare nel container in esecuzione
docker exec -it my-angular-container sh
# Verificare i file serviti
docker exec my-angular-container ls -la /usr/share/nginx/html
# Testare la risposta nginx dall'interno del container
docker exec my-angular-container wget -qO- http://localhost/
# Analizzare i layer dell'immagine
docker history my-angular-app:latest
# Controllare la dimensione dell'immagine
docker images my-angular-app
9. Immagini di Produzione vs Sviluppo
E importante mantenere Dockerfile separati per produzione e sviluppo, poichè hanno requisiti molto diversi:
Confronto Produzione vs Sviluppo
| Caratteristica | Produzione | Sviluppo |
|---|---|---|
| Immagine base | nginx:alpine | node:alpine |
| Server | nginx | ng serve (webpack-dev-server) |
| Hot reload | No | Si (con volume mount) |
| Source maps | No (o upload a Sentry) | Si |
| Dimensione | ~45 MB | ~500 MB |
| Utente | Non-root | Root (per permessi volume) |
Mai Usare l'Immagine di Sviluppo in Produzione
L'immagine di sviluppo contiene Node.js, npm, e tutte le devDependencies. Oltre ad essere enormemente più grande, espone una superficie di attacco molto maggiore. Usa sempre il Dockerfile multi-stage per la produzione.
10. Build e Run Completo
Riassumiamo i comandi per costruire e avviare l'applicazione in entrambi gli ambienti:
# === PRODUZIONE ===
# Build dell'immagine di produzione
docker build -t my-angular-app:latest .
# Avviare il container
docker run -d \
--name angular-prod \
-p 8080:80 \
--restart unless-stopped \
my-angular-app:latest
# Verificare che funzioni
curl http://localhost:8080
# === SVILUPPO ===
# Avviare con Docker Compose
docker compose up -d
# Verificare i servizi
docker compose ps
# Vedere i log
docker compose logs -f angular-app
# Fermare tutto
docker compose down
Conclusione
In questo articolo abbiamo esplorato Docker dal punto di vista dello sviluppatore frontend. Abbiamo costruito un Dockerfile multi-stage che produce immagini di produzione ottimizzate sotto i 50 MB, configurato nginx per il routing SPA con compressione e caching, e creato un ambiente di sviluppo completo con Docker Compose.
I punti chiave da ricordare sono: usare sempre il multi-stage build per la produzione,
configurare correttamente il .dockerignore, rispettare l'ordine dei COPY
per il layer caching, e mantenere Dockerfile separati per sviluppo e produzione.
Nel prossimo articolo vedremo come integrare Firebase Hosting con GitHub Actions per creare un flusso di deploy automatico completo, con preview channel per le Pull Request e rollback strategy.
Domande frequenti
Perchè Docker è importante per gli sviluppatori frontend?
Docker risolve il problema della non riproducibilità dell'ambiente di sviluppo, garantendo che ogni ambiente esegua lo stesso container con le stesse dipendenze e configurazione.
Quali sono i vantaggi del multi-stage build in Docker per gli sviluppatori frontend?
Il multi-stage build riduce drasticamente la dimensione dell'immagine finale, facendo in modo che non contenga Node.js, npm o le dipendenze, ma solo i file statici compilati e nginx.
Perchè è importante rispettare l'ordine dei COPY nelle istruzioni del Dockerfile?
Rispettare l'ordine dei COPY è fondamentale per il layer caching di Docker, in modo che la cache dell'installazione delle dipendenze non venga invalidata da qualsiasi modifica a qualsiasi file.
Perchè è importante mantenere Dockerfile separati per sviluppo e produzione?
È importante mantenere Dockerfile separati per sviluppo e produzione, poiché hanno requisiti molto diversi, come Node.js, npm e devDependencies nel caso dello sviluppo.

Commenti
Caricamento commenti...
Accedi per commentare