Table of Contents
Introduzione
Le applicazioni web moderne raramente funzionano come un unico processo monolitico. Invece, sono composti da servizi di collaborazione multipli: un frontend, un API, un database, una cache, una coda di messaggi, e forse un corridore di lavoro.
Prerequisiti
Prima di immergersi, assicurarsi di avere il seguente installato sul sistema:
- Motore di controllo[ (versione 20.10 o più recente) – Install Docker
- Docker Compose[] (V2 è ora integrato in Docker CLI; verificare con ] – Install Docker Compose]]
- familiarità fisica[[]] con comandi terminali e sintassi YAML.
Se si prevede di seguire la sezione configurazione SSL, è possibile indicare un nome di dominio sul server.
Comprendere Docker Compose in Profondità
Docker Compose non è solo un semplice “docker run” wrapper. Si tratta di uno strumento dichiarativo che consente di definire servizi, reti, volumi, variabili di ambiente, politiche di riavvio e controlli sanitari in un unico file chiamato . La specifica Compose (ora parte del ]]] Specifica completa])])
Servizi
Ogni immagine del contenitore, i suoi porti, i volumi, l'ambiente e le dipendenze sono definite sotto il tasto . I servizi possono comunicare tra loro tramite una rete di ponti dedicata che Compose crea automaticamente. Ciò significa che non è mai necessario aprire porte host per la comunicazione inter-service - solo il proxy inverso ha bisogno di esporre i porti al mondo esterno.
Reti
Per impostazione predefinita, Compose crea una rete unica per tutti i servizi, dando loro la scoperta di servizi basati su DNS utilizzando il nome del servizio come hostname. Ad esempio, un servizio chiamato può essere raggiunto da altri servizi semplicemente come . È inoltre possibile definire reti personalizzate per l'isolamento - per esempio, mettendo solo il proxy inverso su una rete di pubblico-faccia e mantenendo i contenitori di database su una rete interna.
networks:
frontend:
backend:
services:
nginx:
networks:
- frontend
app:
networks:
- frontend
- backend
db:
networks:
- backend
Volume
I volumi sono il meccanismo preferito per i dati persistenti generati dai contenitori Docker. In Compose, è possibile dichiarare i volumi nominati al livello superiore e montarli in servizi. Questo è essenziale per database, caricamenti di file e qualsiasi servizio di stato.
volumes:
db_data:
services:
postgres:
image: postgres:16
volumes:
- db_data:/var/lib/postgresql/data
Variabili dell'ambiente
Esternalizzare la configurazione utilizzando variabili di ambiente. È possibile codificarli nel file Compose per lo sviluppo, ma per la produzione si dovrebbe usare [[ file o segreti Docker. Compose carica automaticamente un file situato nella stessa directory se non si specifica .
services:
api:
image: my-api:latest
env_file:
- ./api.env
environment:
- NODE_ENV=production
Configurare Nginx come un proxy inverso – Avanzato
In un'architettura multicontainer, Nginx si trova al bordo, accetta le richieste dei clienti, e li inoltra al servizio appropriato basato sulla richiesta URI, intestazioni, o anche il contenuto del corpo. Può anche gestire la terminazione SSL, la cache, il limite di velocità e il bilanciamento del carico.
Proxy inverso di base con a monte multipli
Ecco una configurazione più completa che dimostra come dividere il traffico tra due diverse applicazioni, tra cui la gestione di beni statici e WebSockets:
upstream app1_upstream {
server app1:8000;
}
upstream app2_upstream {
server app2:8001;
}
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# App 1 – main web frontend
location / {
proxy_pass http://app1_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# App 2 – admin dashboard
location /admin/ {
proxy_pass http://app2_upstream/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# WebSocket support (e.g., for live updates)
location /ws/ {
proxy_pass http://app1_upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Static assets – serve directly for better performance
location /static/ {
alias /var/www/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
Si noti l'uso di blocchi , che permettono di definire un gruppo di server per il bilanciamento del carico. Anche con un singolo server, utilizzando un gruppo a monte, è facile aggiungere più repliche in seguito senza toccare il blocco server.
SSL/TLS con Let's Encrypt
Per la produzione, il servizio HTTPS non è negoziabile. È possibile automatizzare il rinnovo del certificato utilizzando Certbot o [ acme.sh] all'interno di un contenitore sidecar. Un modello comune è quello di montare i certificati come volumi nel contenitore Nginx.
services:
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./certs:/etc/nginx/certs:ro
- ./static:/var/www/static:ro
ports:
- "80:80"
- "443:443"
depends_on:
- app1
- app2
Per il rinnovo automatico, è possibile aggiungere un contenitore di compagnia come o che gestisce un lavoro di cron e ricarica Nginx quando i certificati sono rinfrescati.
Bilanciamento del carico e controlli sanitari
Quando si scala un servizio a più repliche, Nginx può distribuire richieste utilizzando rotonde, meno connessioni o hash IP. Combinare questo con l'opzione di Docker Compose :
upstream api_servers {
least_conn;
server api:80 max_fails=3 fail_timeout=30s;
}
Per rilevare contenitori non sani, Nginx può essere configurato con [] (richiede NGINX Plus o utilizzare il [] dalla versione open source con una piccola soluzione di lavoro).
Costruire il file composto Docker – Un esempio reale-mondo
Uniamo tutto in un pratico file Compose per un'applicazione web composta da un Node.js API, un frontend React servito da Nginx, un database PostgreSQL e una cache Redis. Il contenitore Nginx servirà file statici e richieste API proxy.
version: "3.9"
services:
nginx:
image: nginx:alpine
container_name: reverse-proxy
restart: unless-stopped
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./frontend/build:/var/www/frontend:ro
- ./certs:/etc/nginx/certs:ro
ports:
- "80:80"
- "443:443"
depends_on:
- api
- frontend
networks:
- public
frontend:
image: my-frontend:latest
# In production, frontend is built into static files and served by Nginx
# This container may run a dev server, but Nginx will bypass it for static files
container_name: frontend-dev
ports:
- "3000:3000"
networks:
- public
# healthcheck: ...
api:
image: my-api:latest
container_name: api-server
restart: unless-stopped
env_file:
- ./api/.env.production
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
networks:
- public
- internal
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
interval: 30s
timeout: 10s
retries: 3
postgres:
image: postgres:16-alpine
container_name: database
restart: unless-stopped
environment:
POSTGRES_USER: app_user
POSTGRES_DB: app_db
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- pgdata:/var/lib/postgresql/data
secrets:
- db_password
networks:
- internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app_user"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: cache
restart: unless-stopped
volumes:
- redisdata:/data
networks:
- internal
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
volumes:
pgdata:
redisdata:
networks:
public:
internal:
internal: true
secrets:
db_password:
file: ./secrets/db_password.txt
Punti chiave in questo file:
- Istituire la rete[]: La rete è definita come [], il che significa che solo i servizi connessi ad essa possono comunicare.
- Segreti[[]]: I dati sensibili come password vengono memorizzati come segreti Docker, montati come file all'interno del contenitore, evitando così di perderli nelle variabili di ambiente che possono essere esposti tramite .
- Controlli di salute[[]: I servizi dichiarano i controlli sanitari in modo che []] possa aspettare la condizione [[]. Nginx inizierà solo dopo che l'API è sano.
- Static Asset[]: L'uscita di frontend build viene montata direttamente in Nginx, permettendo a Nginx di servire file statici senza colpire il server frontnd dev.
Distribuzione dell'applicazione – Passo dopo Passo
Con la configurazione Compose e Nginx pronta, l'implementazione comporta alcuni semplici comandi. In primo luogo, assicurarsi che tutti i file di configurazione siano in posizione:
project/
├── docker-compose.yml
├── nginx/
│ └── nginx.conf
├── api/
│ └── .env.production
├── frontend/
│ └── build/
├── certs/
│ └── (fullchain.pem, privkey.pem)
└── secrets/
└── db_password.txt
Allora correte:
docker compose pull # pull latest images
docker compose up -d # start all services in background
docker compose ps # verify services are running
docker compose logs -f # tail logs for all services
Controllare i log Nginx per confermare le richieste sono state correttamente proxied. Se è necessario aggiornare le variabili di configurazione o ambiente, modificare i file pertinenti ed eseguire:
docker compose up -d --no-deps --build nginx # rebuild only nginx if config changed
Per le distribuzioni a tempo zero, considerare l'utilizzo blu-verde] o [] aggiornamenti di registrazione[[]]] con la funzione Compose ] e Nginx controlli sanitari a monte.
Tuning di scala e prestazioni
Docker Compose lo rende banale per scalare servizi senza condizioni come l'API:
docker compose up -d --scale api=3
Ora sono in esecuzione tre repliche del contenitore API. Nginx, configurato con il blocco a monte, distribuirà automaticamente le richieste tra di loro.
- Abilita la compressione Gzip[[] in Nginx per le risposte basate sul testo.
- Impostare le intestazioni cache corrette[] per le attività statiche (come mostrato nell'esempio).
- Utilizzare un CDN[] per la consegna globale di file statici.
- Tune Nginx processi di lavoro[[] per abbinare i core della CPU:
- Aumentare i limiti di connessione[]:
Per i database, assicurarsi di avere connessione pooling nella vostra API e considerare l'utilizzo di PgBouncer come un contenitore sidecar.
Risoluzione dei problemi Problemi comuni
Anche con una configurazione ben strutturata, le cose possono andare storte. Qui ci sono frequenti insidie e come risolverle:
- Il cliente non può raggiungere un altro servizio per nome[[: Controllare che entrambi i servizi siano sulla stessa rete Docker.
- Nginx restituisce 502 Bad Gateway[[]: Il contenitore a monte potrebbe non essere in esecuzione o non sta ascoltando sulla porta prevista. Verificare con . Inoltre assicurarsi che l'URL proxy pass include il protocollo corretto e la porta.
- Esatti di ammissione con volumi[[]: I file montati in contenitori ereditano la proprietà dell'host. Utilizzare namespace utente o impostare la direttiva in Nginx e il contenitore di servizio.
- Certificato SSL non rinnovante[[]: Se si utilizza il certbot standalone, assicurarsi che le porte 80 e 443 non siano bloccate da Nginx durante il rinnovo.
- Le variabili di avanzamento non caricate[[]: Verificare che il file [ sia nella posizione corretta o utilizzare la direttiva . È possibile stampare inv all'interno del contenitore con .
Migliori Pratiche di Sicurezza
Le applicazioni multi-container in produzione richiedono vigilanza:
- Non eseguire mai contenitori come root[] a meno che non sia necessario. Utilizzare la direttiva o specificare un nel Dockerfile.
- Aggiungi immagini fino ad oggi[[] con scansione regolare della vulnerabilità.
- Ristrict network presentation[]: Esporre solo le porte proxy inversa a Internet. Tutti gli altri servizi dovrebbero essere su reti interne.
- Usa gestione dei segreti[[] per password, chiavi API e gettoni. I segreti di Docker sono un buon inizio; per le configurazioni più grandi, prendere in considerazione HashiCorp Vault.
- Abilita il Contenuti Docker[] per verificare le firme dell'immagine.
- Regolelmente log and audit[] attività di container utilizzando [] o una soluzione di registrazione centralizzata come lo stack ELK.
- Applicare i limiti delle risorse[ nel file Compose per impedire a un singolo contenitore di affamare gli altri:
services:
api:
deploy:
resources:
limits:
cpus: '0.50'
memory: 256M
reservations:
cpus: '0.25'
memory: 128M
Questi limiti sono rispettati quando si utilizza lo sciame Docker; per il Compose normale, sono applicati con [ (anche se è meglio usare lo sciame o K8 per l'orchestrazione di produzione).
Conclusioni
Con la definizione della pila in un unico file YAML, si ottiene la parità di ambiente dallo sviluppo alla produzione. Nginx, come una base inversa, fornisce un punto centrale per la terminazione SSL, il routing del traffico, il bilanciamento del carico e il caching.