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.