Civil & Structural Engineering
Deploying Multi-container Web Applications with Docker Compose and Nginx
Table of Contents
Présentation
Les applications web modernes fonctionnent rarement comme un seul processus monolithique. Elles sont plutôt composées de multiples services collaborateurs : une interface, une API, une base de données, un cache, une file d'attente de messages et peut-être un coureur de travail. La gestion manuelle du cycle de vie de chaque conteneur, de leur réseau et de leurs dépendances devient rapidement sujette aux erreurs et prend du temps. Docker Compose et Nginx offrent ensemble une solution facile à produire et éprouvée par la bataille pour définir et déployer ces systèmes multicontainers avec un frottement minimal. Docker Compose vous permet de décrire toute votre pile d'application dans un seul fichier YAML, tandis que Nginx agit comme un mandataire inverseur fiable et un équilibreur de charge, acheminant le trafic vers le service approprié.
Préalables
Avant de plonger dans le système, assurez-vous que vous avez installé les éléments suivants sur votre système :
- Moteur à double sens (version 20.10 ou plus récent) – Installer Docker
- Compose de Docker (V2 est maintenant intégré dans Docker CLI; vérifier avec ) – Installer Docker Compose
- Familiarité de base avec les commandes terminales et la syntaxe YAML.
Si vous prévoyez de suivre la section de configuration SSL, vous pouvez également avoir un nom de domaine pointant vers votre serveur.
Comprendre Docker Compose en profondeur
Docker Compose n'est pas seulement un simple -enrouleur -encombre. C'est un outil déclaratif qui vous permet de définir des services, des réseaux, des volumes, des variables d'environnement, des politiques de redémarrage et des contrôles de santé dans un seul fichier appelé . La spécification Compose (maintenant partie du Compose Specification) standardise la description des applications multi-conteneurs, rendant votre configuration portable sur différents pipelines CI/CD et fournisseurs de cloud.
Services
Chaque image conteneur, ses ports, volumes, environnement et dépendances sont définis sous la touche . Les services peuvent communiquer entre eux via un réseau de ponts dédié que Compose crée automatiquement. Cela signifie que vous n'avez jamais besoin d'ouvrir les ports hôtes pour la communication interservice — seul le proxy inversé doit exposer les ports au monde extérieur.
Réseaux
Par défaut, Compose crée un réseau unique pour tous les services, leur donnant la découverte de service DNS en utilisant le nom de service comme nom d'hôte. Par exemple, un service nommé peut être atteint par d'autres services simplement comme . Vous pouvez également définir des réseaux personnalisés pour l'isolement — par exemple, en ne mettant que le proxy inverse sur un réseau public et en conservant des conteneurs de base de données sur un réseau interne.
networks:
frontend:
backend:
services:
nginx:
networks:
- frontend
app:
networks:
- frontend
- backend
db:
networks:
- backend
Volumes
Les volumes sont le mécanisme privilégié pour les données persistantes générées par les conteneurs Docker. Dans Compose, vous pouvez déclarer les volumes nommés au niveau supérieur et les monter dans les services. Ceci est essentiel pour les bases de données, les téléchargements de fichiers et tout service d'état.
volumes:
db_data:
services:
postgres:
image: postgres:16
volumes:
- db_data:/var/lib/postgresql/data
Variables d'environnement
Externaliser la configuration en utilisant des variables d'environnement. Vous pouvez les coder en dur dans le fichier Composer pour le développement, mais pour la production vous devriez utiliser des fichiers ou des secrets Docker. Composer charge automatiquement un fichier situé dans le même répertoire si vous ne spécifiez pas .
services:
api:
image: my-api:latest
env_file:
- ./api.env
environment:
- NODE_ENV=production
Configuration de Nginx comme un mandataire inversé – avancé
Nginx brille comme un proxy inversé en raison de ses hautes performances, de son empreinte mémoire faible et de son ensemble de fonctionnalités riches. Dans une architecture multicontainer, Nginx se trouve au bord, accepte les demandes du client et les transmet au service approprié en fonction de l'URI de demande, des en-têtes, ou même du contenu corporel.
Proxy inverse de base avec plusieurs flux en amont
Voici une configuration plus complète qui montre comment répartir le trafic entre deux applications différentes, y compris la gestion des actifs statiques et 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";
}
}
Remarquez l'utilisation de blocs . Ils vous permettent de définir un groupe de serveurs pour l'équilibrage de charge. Même avec un seul serveur, l'utilisation d'un groupe en amont facilite l'ajout de répliques plus tard sans toucher le bloc serveur.
SSL/TLS avec Encryptons
Pour la production, le service HTTPS n'est pas négociable. Vous pouvez automatiser le renouvellement de certificat en utilisant Certbot ou acme.sh à l'intérieur d'un conteneur sidecar. Un modèle commun est de monter les certificats en volumes dans le conteneur Nginx. Par exemple:
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
Pour le renouvellement automatique, vous pouvez ajouter un conteneur comme ou qui exécute un travail de cron et recharge Nginx lorsque les certificats sont rafraîchis.
Équilibre de charge et contrôles de santé
Lorsque vous adaptez un service à plusieurs répliques, Nginx peut distribuer des requêtes en utilisant un robin rond, des connexions moins importantes ou un hachage IP. Combinez ceci avec l'option Docker Compose , :
upstream api_servers {
least_conn;
server api:80 max_fails=3 fail_timeout=30s;
}
Pour détecter les contenants malsains, Nginx peut être configuré avec (qui nécessite NGINX Plus ou utilise le de la version open-source avec un petit contournement). Une solution plus simple est de se fier aux contrôles de santé de Docker et de n'enregistrer que les contenants qui passent.
Construire le fichier composé de Docker – Un exemple du monde réel
Let="s combine tout en un fichier Compose pratique pour une application web composée d'une API Node.js, d'un frontend React desservi par Nginx, d'une base de données PostgreSQLTM et d'un cache Redis. Le conteneur Nginx servira des requêtes d'API statiques et 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
Points clés de ce fichier :
- Isolation du réseau: Le réseau est défini comme , ce qui signifie que seuls les services connectés à lui peuvent communiquer. La base de données et le cache ne sont pas accessibles de l'extérieur de la superposition Docker.
- Secrets: Des données sensibles comme les mots de passe sont stockées comme des secrets Docker, montés comme des fichiers à l'intérieur du conteneur. Cela évite de les fuiter dans des variables d'environnement qui peuvent être exposées via .
- : Les services déclarent des contrôles de santé afin que puisse attendre la condition . Nginx ne commencera qu'après que l'API est en bonne santé.
- Assets statiques: La sortie de construction frontend est montée directement dans Nginx, permettant à Nginx de servir des fichiers statiques sans toucher le serveur frontend dev. Cela améliore les performances et sépare les préoccupations.
Déploiement de l'application – étape par étape
Avec le fichier Compose et la configuration Nginx prêts, le déploiement implique quelques commandes simples.
project/
├── docker-compose.yml
├── nginx/
│ └── nginx.conf
├── api/
│ └── .env.production
├── frontend/
│ └── build/
├── certs/
│ └── (fullchain.pem, privkey.pem)
└── secrets/
└── db_password.txt
Puis lancez :
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
Une fois tout est terminé, testez l'application en visitant votre domaine. Vérifiez que les journaux Nginx confirment que les requêtes sont correctement proxées. Si vous devez mettre à jour les variables de configuration ou d'environnement, modifiez les fichiers pertinents et exécutez :
docker compose up -d --no-deps --build nginx # rebuild only nginx if config changed
Pour les déploiements à temps zéro, envisager d'utiliser bleu-vert ou des mises à jour de roulement[ avec la fonction Compose=] et les contrôles de santé en amont de Nginx.
Élargissement et réglage des performances
Docker Compose rend trivial d'étendre les services apatrides comme l'API :
docker compose up -d --scale api=3
Maintenant trois répliques du conteneur API sont en cours d'exécution. Nginx, configuré avec le bloc en amont, distribuera automatiquement les requêtes parmi eux. Pour améliorer encore les performances:
- Activer la compression Gzip[ dans Nginx pour les réponses texte.
- Filtre les en-têtes appropriés du cache pour les actifs statiques (comme indiqué dans l'exemple).
- Utilisez un CDN pour la livraison globale de fichiers statiques.
- Tune Nginx traite les processus [ pour correspondre aux cœurs du processeur:
- Augmentation des limites de connexion[:
Pour les bases de données, assurez-vous d'avoir une connexion dans votre API et envisagez d'utiliser PgBoncer comme conteneur de sidecar.
Dépannage de problèmes communs
Même avec une configuration bien structurée, les choses peuvent mal tourner. Voici des pièges fréquents et comment les réparer:
- Container ne peut pas atteindre un autre service par son nom[ : Vérifiez que les deux services sont sur le même réseau Docker. Utilisez pour tester.
- Nginx retourne 502 Bad Gateway : Le conteneur en amont peut ne pas fonctionner ou ne pas écouter sur le port prévu. Vérifiez avec . Assurez-vous également que l'URL proxy pass inclut le protocole et le port corrects.
- Erreurs de permission avec des volumes: Les fichiers montés dans des conteneurs héritent de la propriété de l'hôte. Utilisez des espaces de noms d'utilisateur ou définissez la directive dans Nginx et le conteneur de service.
- Certificat SSL ne renouvelant pas: Si vous utilisez certbot standalone, assurez-vous que les ports 80 et 443 ne sont pas bloqués par Nginx pendant le renouvellement.
- Les variables environnementales ne sont pas chargées: Vérifiez que le fichier est au bon endroit ou utilisez la directive correctement. Vous pouvez imprimer env à l'intérieur du conteneur avec .
Pratiques exemplaires en matière de sécurité
La mise en service d'applications multiconteneurs dans la production nécessite une vigilance:
- Ne jamais exécuter de conteneurs comme racine sauf si nécessaire. Utilisez la directive ou spécifiez une dans le fichier Docker.
- Gardez les images à jour avec un balayage de vulnérabilité régulier. Utilisez les images de base officielles et les versions de broche.
- Restriction de l'exposition au réseau: Ne exposer que les ports proxy inversés à Internet. Tous les autres services devraient être sur des réseaux internes.
- Utilisez la gestion des secrets pour les mots de passe, les clés API et les jetons. Les secrets Docker sont un bon début ; pour les configurations plus grandes, considérez HashiCorp Vault.
- Activer Docker Content Trust[ pour vérifier les signatures d'image.
- Loger et vérifier régulièrement l'activité du conteneur en utilisant ou une solution centralisée de l'enregistrement comme la pile ELK.
- Appliquez les limites de ressources[ dans votre fichier Composez pour empêcher un seul conteneur de mourir de faim :
services:
api:
deploy:
resources:
limits:
cpus: '0.50'
memory: 256M
reservations:
cpus: '0.25'
memory: 128M
Ces limites sont respectées lors de l'utilisation de l'essaim Docker; pour Compose simple, elles sont imposées avec (bien qu'il soit préférable d'utiliser l'essaim ou les K8 pour l'orchestration de production).
Conclusion
En définissant votre pile dans un seul fichier YAML, vous obtenez la parité environnementale du développement à la production. Nginx, comme mandataire inverse, fournit un point central pour la terminaison SSL, le routage du trafic, l'équilibrage de charge et le cache. Nous avons couvert les composants essentiels : définitions de service, mise en réseau, volumes, variables d'environnement, configuration Nginx avec optimisations de sécurité et de performance, conseils de mise à l'échelle et dépannage. Avec cette fondation, vous êtes prêt à concevoir, déployer et exploiter des applications conteneurisées modernes avec confiance. Pour plus de détails, consultez la documentation Docker Compose et la documentation Nginx, et explorez des exemples du monde réel provenant de l'exemple de Compose impressionnant pour vous inspirer.