Docker en Production : Maîtriser les Containers pour des Déploiements Robustes
Plongez dans les patterns avancés de Docker utilisés par les équipes DevOps professionnelles. Apprenez à construire, optimiser et déployer des containers en production avec les meilleures pratiques de l'industrie.
1. Architecture Multi-Stage et Optimisation des Images
Contexte
La construction d'images Docker efficaces est cruciale en production. Les images volumineuses ralentissent les déploiements, consomment plus de bande passante et augmentent les risques de sécurité. Les builds multi-stage permettent de séparer l'environnement de compilation de l'environnement d'exécution, réduisant drastiquement la taille finale tout en maintenant la sécurité. C'est une pratique incontournable dans les CI/CD modernes où chaque KB compte.
Bloc Code Commenté
# Stage 1 : Build environment (compilation)
FROM node:18-alpine AS builder
WORKDIR /app
# Copie les fichiers de dépendances
COPY package*.json ./
# Installation des dépendances (inclut les devDependencies)
RUN npm ci
# Copie le code source
COPY . .
# Build de l'application
RUN npm run build
# Stage 2 : Runtime environment (exécution)
FROM node:18-alpine
WORKDIR /app
# Variables d'environnement de production
ENV NODE_ENV=production
# Copie uniquement les fichiers nécessaires du stage builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
# User non-root pour la sécurité
USER node
# Port d'exposition
EXPOSE 3000
# Commande de démarrage
CMD ["node", "dist/index.js"]
Cas Réel Professionnel
Une startup fintech avait une image Node.js de 850MB en production. Après implémentation des builds multi-stage et utilisation d'Alpine, elle est passée à 120MB. Résultat : temps de pull 7x plus rapide, consommation mémoire réduite de 40%, et sécurité améliorée par la réduction de la surface d'attaque. Leurs déploiements Kubernetes passèrent de 3 minutes à 45 secondes.
Astuce Pro
Utilisez toujours npm ci au lieu de npm install dans vos Dockerfiles. Cela installe les versions exactes du fichier package-lock.json, garantissant une reproductibilité absolue à travers les environnements. C'est une différence invisible mais critique en production.
2. Gestion des Secrets et Variables d'Environnement
Contexte
Exposer des secrets (API keys, mots de passe, tokens) dans les Dockerfiles ou les images est une faille de sécurité majeure. En production, la distinction entre les configurations (variables d'environnement) et les secrets (données sensibles) est fondamentale. Docker propose plusieurs mécanismes pour cette gestion, du simple .env aux Docker Secrets en orchestration, chacun adapté à un contexte spécifique. Les équipes DevOps professionnelles ne commettent jamais d'erreur à ce sujet.
Bloc Code Commenté
# Dockerfile SECURISE - ne jamais passer les secrets en ARG ou ENV
FROM python:3.11-slim
WORKDIR /app
# ARG pour les valeurs non-sensibles uniquement (elles disparaissent après build)
ARG BUILD_VERSION=1.0.0
ENV APP_VERSION=${BUILD_VERSION}
# Variables d'environnement vides - seront remplies à runtime
ENV DATABASE_URL=""
ENV API_KEY=""
ENV LOG_LEVEL="INFO"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Healthcheck pour vérifier que le container fonctionne
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:5000/health')"
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
Exemple de docker-compose avec gestion des secrets
version: '3.9'
services:
api:
image: myapp:latest
environment:
# Variables non-sensibles
LOG_LEVEL: "INFO"
APP_ENV: "production"
# Secrets chargés depuis un fichier externe
DATABASE_URL: ${DATABASE_URL}
API_KEY: ${API_KEY}
# Montage d'un fichier .env (ne jamais commit en Git !)
env_file:
- .env.production
secrets:
- db_password
ports:
- "5000:5000"
secrets:
db_password:
# En production, utiliser Docker Swarm secrets ou Kubernetes secrets
file: ./secrets/db_password.txt
Cas Réel Professionnel
Une agence web a souffert d'une fuite de données : une API key AWS était loggée dans l'historique Docker. L'attaquant a eu accès aux ressources cloud pendant 4 heures, coûtant $15,000 en usage non autorisé. Après incident, ils ont mis en place : validation de contenu des images pré-push, scan de secrets avec truffleHog, et utilisation stricte de Docker Secrets en production. Zéro incident depuis 2 ans.
Astuce Pro
Utilisez docker secret avec Docker Swarm ou les secrets Kubernetes. Ces systèmes de gestion des secrets garantissent que les données sensibles ne sont jamais écrites sur le disque du container et sont chiffrées en transit et au repos. Pour le développement local, utilisez des fichiers .env exclos de Git avec une ligne dans .gitignore.
3. Networking et Communication Inter-Containers
Contexte
En production, les containers rarement fonctionnent isolés. Ils forment une architecture distribuée où chaque service doit communiquer avec les autres de manière fiable, sécurisée et performante. Docker propose des réseaux (bridge, host, overlay) avec des capacités DNS intégrées et d'équilibrage de charge. Comprendre ces mécanismes est vital pour construire des applications microservices robustes. Une mauvaise configuration peut mener à des problèmes latents détectés seulement en production.
Bloc Code Commenté
# Docker-compose avec networking avancé
version: '3.9'
services:
# Service frontend
frontend:
image: nginx:alpine
# Réseau personnalisé = service discovery DNS automatique
networks:
- app-network
ports:
# Port publié uniquement pour le frontend
- "80:80"
environment:
# Communication interne via nom du service
BACKEND_HOST: backend:3000
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- backend
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
# Service backend
backend:
image: myapp:latest
networks:
- app-network
# Port non exposé publiquement (accessible uniquement via le réseau interne)
expose:
- "3000"
environment:
DATABASE_HOST: db
DATABASE_PORT: 5432
REDIS_HOST: cache
REDIS_PORT: 6379
# Communication inter-service via hostname
LOG_SERVICE: logs:8080
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
# Base de données
db:
image: postgres:15-alpine
networks:
- app-network
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
# Mot de passe depuis un secret
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
# Volume nommé pour la persistance des données
- db-data:/var/lib/postgresql/data
# Script d'initialisation
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
expose:
- "5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
# Cache Redis
cache:
image: redis:7-alpine
networks:
- app-network
expose:
- "6379"
# Configuration Redis via paramètres de commande
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
- cache-data:/data
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 10s
timeout: 5s
retries: 3
# Service de logs centralisé
logs:
image: elasticsearch:7.14.0
networks:
- app-network
environment:
discovery.type: single-node
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
expose:
- "8080"
volumes:
- logs-data:/usr/share/elasticsearch/data
# Réseau personnalisé pour l'application
networks:
app-network:
driver: bridge
# Configuration pour meilleure isolation et sécurité
driver_opts:
com.docker.network.bridge.name: br-app
com.docker.network.bridge.enable_ip_masquerade: "true"
ipam:
config:
- subnet: 172.25.0.0/16
gateway: 172.25.0.1
# Volumes nommés pour persistence
volumes:
db-data:
driver: local
cache-data:
driver: local
logs-data:
driver: local
Cas Réel Professionnel
Une plateforme SaaS avait des connexions qui "tombaient" aléatoirement entre le backend et la base de données. Investigation : ils utilisaient localhost en variable d'environnement au lieu du nom de service. Quand Docker devait réallouer les containers, les IPs changeaient mais le code cherchait toujours localhost. Solution : utiliser les noms de service et laisser le DNS intégré résoudre les IPs. Après correction, zéro déconnexion en 6 mois.
Astuce Pro
Ne publiez (avec ports:) que les services qui doivent être accessibles de l'extérieur. Utilisez expose: pour les services internes. Cela crée une "zone démilitarisée" naturelle où seul le frontend est exposé, renforçant la sécurité. De plus, utilisez les noms de service (DNS) plutôt que les IPs : le réseau Docker gère automatiquement les changements.
4. Volumes, Montages et Persistance des Données
Contexte
Les containers sont par nature éphémères : supprimer un container efface ses données internes. Pourtant, en production, les données doivent persister à travers les redémarrages, mises à jour, et défaillances. Docker propose trois mécanismes : les volumes (gérés par Docker), les bind mounts (répertoires hôte), et les tmpfs (RAM). Chacun a des cas d'usage distincts. Une mauvaise stratégie de persistance peut causer des pertes de données critiques ou des problèmes de performance sévères.
Bloc Code Commenté
version: '3.9'
services:
# Application web stateful
webapp:
image: myapp:latest
volumes:
# Volume nommé pour les données persistentes
- app-data:/app/data
# Bind mount pour la configuration (lisible depuis l'hôte)
- ./config.json:/app/config.json:ro
# Dossier de logs avec bind mount (pour debug)
- ./logs:/app/logs
# tmpfs pour les fichiers temporaires (performant, en RAM)
- type: tmpfs
target: /app/tmp
tmpfs:
size: 512M
environment:
DATA_PATH: /app/data
CONFIG_PATH: /app/config.json
# Base de données avec stratégie de backup
postgres:
image: postgres:15-alpine
volumes:
# Volume nommé = géré par Docker, recommandé pour production
- postgres-data:/var/lib/postgresql/data
# Scripts d'initialisation (bind mount, en read-only)
- ./init-db:/docker-entrypoint-initdb.d:ro
# Dossier de backup (bind mount, read-write)
- ./backups:/backups
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Service de backup automatique
backup:
image: postgres:15-alpine
# Dépend de la DB pour s'assurer qu'elle est up
depends_on:
postgres:
condition: service_healthy
volumes:
# Accès au dossier de backup
- ./backups:/backups
# Script de backup
- ./scripts/backup.sh:/backup.sh:ro
environment:
PGPASSWORD: ${DB_PASSWORD}
# Commande cron-like pour backups périodiques
command: >
sh -c "
while true; do
echo 'Starting backup at '$(date);
pg_dump -h postgres -U postgres postgres |
gzip > /backups/backup_$(date +%Y%m%d_%H%M%S).sql.gz;
echo 'Backup completed';
sleep 86400;
done
"
volumes:
# Volume nommé avec driver local (stockage hôte)
app-data:
driver: local
driver_opts:
type: none
o: bind
device: ./app_data
# Volume pour PostgreSQL avec options de performance
postgres-data:
driver: local
driver_opts:
type: tmpfs
o: size=2G
# Alternative pour stockage disque : device: /mnt/docker-volumes/postgres
# Script d'initialisation PostgreSQL
# File: ./init-db/01-init.sql
# CREATE SCHEMA IF NOT EXISTS app;
# CREATE TABLE app.users (
# id SERIAL PRIMARY KEY,
# username VARCHAR(100) NOT NULL UNIQUE,
# created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
# );
Cas Réel Professionnel
Une startup fintech a perdu 48 heures de données transactionnelles. Raison : ils utilisaient un bind mount pour PostgreSQL, et l'administrateur serveur a accidentellement supprimé le répertoire hôte pendant une maintenance. Les données étaient parties définitivement. Après incident, migration vers des volumes nommés avec snapshots quotidiens, et test mensuel de restauration depuis backup. Plus jamais de panique.
Astuce Pro
Préférez les volumes nommés aux bind mounts pour les données de production. Les volumes sont gérés par Docker, chiffrés, et supportent les snapshots/backups. Les bind mounts sont utiles seulement pour du développement local ou des configurations en read-only. Et toujours : testez régulièrement vos restaurations depuis backup. Un backup jamais testé n'en est pas un.
5. Logging, Monitoring et Observabilité
Contexte
Un container qui plante silencieusement est une cauchemar opérationnel. En production, il faut visibilité complète : logs applicatifs, métriques système (CPU, mémoire), traces distribuées, et alertes. Docker supporte plusieurs approches : les drivers de logging (json-file, syslog, splunk), les healthchecks, et l'intégration avec des stacks de monitoring (Prometheus, ELK, Jaeger). Une observabilité faible = diagnostic long et coûteux en cas de panne.
Bloc Code Commenté
version: '3.9'
services:
# Application avec logging configuré
app:
image: myapp:latest
# Configuration du driver de logging
logging:
# Driver JSON structuré (recommandé pour production)
driver: json-file
options:
max-size: "10m" # Rotation tous les 10MB
max-file: "3" # Garder 3 fichiers max
labels: "app=myapp,env=production"
# Exposition des métriques pour Prometheus
ports:
- "9090:9090" # Port des métriques
environment:
LOG_LEVEL: "INFO"
METRICS_ENABLED: "true"
METRICS_PORT: "9090"
# Healthcheck pour détection de problèmes
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- monitoring
# Stack ELK pour logs centralisés
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.14.0
environment:
discovery.type: single-node
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
xpack.security.enabled: "false"
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
networks:
- monitoring
healthcheck:
test: ["CMD-SHELL", "curl -s http://localhost:9200 >/dev/null || exit 1"]
interval: 30s
timeout: 10s
retries: 3
logstash:
image: docker.elastic.co/logstash/logstash:7.14.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro
environment:
LS_JAVA_OPTS: "-Xmx256m -Xms256m"
depends_on:
elasticsearch:
condition: service_healthy
networks:
- monitoring
ports:
- "5000:5000"
kibana:
image: docker.elastic.co/kibana/kibana:7.14.0
environment:
ELASTICSEARCH_HOSTS: "http://elasticsearch:9200"
ELASTICSEARCH_USERNAME: "elastic"
ELASTICSEARCH_PASSWORD: "changeme"
ports:
- "5601:5601"
depends_on:
elasticsearch:
condition: service_healthy
networks:
- monitoring
# Prometheus pour métriques
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
ports:
- "9091:9090"
networks:
- monitoring
# Grafana pour visualisation
grafana:
image: grafana/grafana:latest
environment:
GF_SECURITY_ADMIN_PASSWORD: "admin"
GF_USERS_ALLOW_SIGN_UP: "false"
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
- ./grafana-dashboards:/etc/grafana/provisioning/dashboards:ro
depends_on:
- prometheus
networks:
- monitoring
volumes:
elasticsearch-data:
prometheus-data:
grafana-data:
networks:
monitoring:
driver: bridge
Fichier logstash.conf
# Configuration Logstash pour traiter les logs Docker
input {
tcp {
port => 5000
codec => json
}
}
filter {
# Parser les timestamps ISO
date {
match => [ "timestamp", "ISO8601" ]
target => "@timestamp"
}
# Enrichissement avec labels Docker
mutate {
add_field => { "[@metadata][index_name]" => "logs-%{+YYYY.MM.dd}" }
}
}
output {
# Envoi vers Elasticsearch
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "%{[@metadata][index_name]}"
}
# Stdout pour debug
stdout {
codec => rubydebug
}
}
Cas Réel Professionnel
Une plateforme de commerce électronique avait des ralentissements intermittents. Sans observabilité, l'équipe tâtonnait. Après déploiement d'une stack ELK + Prometheus + Grafana, ils ont découvert qu'une requête N+1 sur le service catalogue causait des pics de CPU tous les 15 minutes. Correction simple (cache Redis) qui n'aurait jamais été trouvée sans visibilité complète. ROI du monitoring : 10x.
Astuce Pro
Configurez les limites de taille des logs (max-size, max-file) pour éviter que les disques ne se remplissent. De plus, parsez les logs en JSON structuré (avec champs comme timestamp, level, service, trace_id) plutôt que du texte libre : cela permet des requêtes et agrégations bien plus puissantes dans Elasticsearch ou Loki.
6. Sécurité des Containers : Images, Registres et Scans
Contexte
Les containers héritent des vulnérabilités du code, des dépendances, et des images de base. En production, ignorer la sécurité est criminel : une image compromise peut exposer tous les secrets, données, et infrastructure. Les équipes DevOps professionnelles scannent les images à la construction, stockent les images dans des registres privés/sécurisés, utilisent des images de base minimales (Alpine, distroless), et mettent à jour régulièrement. La sécurité n'est pas optionnelle, c'est une obligation légale (RGPD, SOC2, PCI-DSS).
Bloc Code Commenté
# Dockerfile SECURISE avec scan intégré
FROM python:3.11-slim-bookworm
# Labels pour traçabilité
LABEL maintainer="devops@company.com"
LABEL version="1.0.0"
LABEL description="Production-ready Python app with security hardening"
# Update et patch des vulnérabilités connues
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install --no-install-recommends -y \
ca-certificates \
curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Créer un utilisateur non-root (crucial pour la sécurité)
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Copier requirements et installer dépendances
COPY requirements.txt .
RUN pip install --no-cache-dir \
--disable-pip-version-check \
-r requirements.txt
# Copier le code applicatif
COPY --chown=appuser:appuser . .
# Changer vers l'utilisateur non-root
USER appuser
# Expose uniquement les ports nécessaires
EXPOSE 8000
# Healthcheck
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Commande avec exec form (PID 1 pour signaux correctement transmis)
ENTRYPOINT ["python"]
CMD ["-u", "app.py"]
Analyse et scan des images
#!/bin/bash
# Script de sécurité pré-deployment
IMAGE="myapp:latest"
# 1. Build l'image
docker build -t ${IMAGE} .
# 2. Scan avec Trivy (détecte CVEs)
echo "=== Scanning image for vulnerabilities with Trivy ==="
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image --severity HIGH,CRITICAL ${IMAGE}
# 3. Scan avec Snyk (dépendances et code)
echo "=== Scanning dependencies with Snyk ==="
snyk container test ${IMAGE} --fail-on=high
# 4. Audit avec Anchore (scan complet)
echo "=== Analyzing with Anchore ==="
anchore-cli image add ${IMAGE}
anchore-cli image wait ${IMAGE}
anchore-cli image vuln ${IMAGE} all
# 5. Inspection manuelle avec docker inspect
echo "=== Docker Image Inspection ==="
docker inspect ${IMAGE} | jq '.[0] | {
Config: .Config.User,
Cmd: .Config.Cmd,
Env: .Config.Env,
Exposed_Ports: .Config.ExposedPorts,
Volumes: .Config.Volumes
}'
# 6. Vérification des couches sensibles
echo "=== Checking layers for secrets ==="
docker history ${IMAGE} --human --no-trunc | grep -E 'RUN|ENV|ARG'
# 7. Scanner les secrets en dur
docker run --rm -i \
-v ~/.truffleHog.json:/root/.truffleHog.json:ro \
trufflesecurity/trufflehog:latest \
filesystem /tmp/image \
--json
# 8. Si tous les tests passent, push vers registre sécurisé
if [ $? -eq 0 ]; then
echo "=== All security checks passed, pushing to registry ==="
docker tag ${IMAGE} registry.company.com/secure/${IMAGE}
docker push registry.company.com/secure/${IMAGE}
else
echo "=== Security checks failed, image rejected ==="
exit 1
fi
Configuration registre privé sécurisé
# docker-compose pour registre Docker privé avec authentification
version: '3.9'
services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
# Authentification basique
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
# TLS/SSL
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
REGISTRY_HTTP_TLS_KEY: /certs/domain.key
# Scan des images à la push
REGISTRY_STORAGE: s3
REGISTRY_STORAGE_S3_BUCKET: docker-registry-images
REGISTRY_STORAGE_S3_REGION: eu-west-1
volumes:
- ./auth:/auth:ro
- ./certs:/certs:ro
- registry-data:/var/lib/registry
networks:
- secure
# Garbage collection pour nettoyer les images inutilisées
registry-cleanup:
image: registry:2
entrypoint: registry garbage-collect
command: /etc/docker/registry/config.yml
volumes:
- ./registry-config.yml:/etc/docker/registry/config.yml:ro
- registry-data:/var/lib/registry
depends_on:
- registry
networks:
- secure
volumes:
registry-data:
networks:
secure:
driver: bridge
Cas Réel Professionnel
Une grande banque a trouvé une backdoor dans l'une de ses images Docker en production : un développeur avait accidentellement inclus des credentials de développement. L'image s'était propagée à 500+ containers. La perte finale estimée : 2 millions de dollars en dédommagement client et frais légaux. Aujourd'hui, tous les builds sont scrutés avec Trivy + Snyk. Aucune image n'est deployée sans scan de sécurité passant. Le coût du scanning ? Négligeable comparé au risque.
Astuce Pro
Utilisez les images distroless (par Google) ou alpine pour minimiser la surface d'attaque. Une image Alpine est souvent 10x plus petite qu'une image classique, donc 10x moins de vulnérabilités potentielles. Combinez avec un scan automatique en CI/CD : si une image a des CVEs critiques, rejetez-la automatiquement avant le déploiement.
7. Orquestration : Docker Compose en Production
Contexte
Docker Compose est présenté souvent comme un outil de développement, mais peut tout à fait être utilisé en production pour des déploiements single-host ou petite équipe. Avec les bonnes pratiques (version 3.9+, secrets, healthchecks, restart policies, resource limits), Compose offre une alternative légère à Kubernetes pour certains contextes. Comprendre Compose à fond inclut la gestion des mises à jour, du scaling limité, et de la haute disponibilité relative.
Bloc Code Commenté
version: '3.9'
# Configuration des secrets (version production)
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
jwt_secret:
file: ./secrets/jwt_secret.txt
services:
# Load balancer / Reverse Proxy
nginx:
image: nginx:1.25-alpine
container_name: prod-nginx
restart: unless-stopped # Redémarrage automatique
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
- nginx-logs:/var/log/nginx
depends_on:
- api-1
- api-2
- api-3
networks:
- production
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 10s
timeout: 5s
retries: 3
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
# API instances (scaling horizontal manual)
api-1:
image: myapp:${APP_VERSION:-latest}
container_name: prod-api-1
restart: unless-stopped
environment:
NODE_ENV: production
INSTANCE_ID: 1
DATABASE_HOST: postgres
DATABASE_NAME: ${DB_NAME}
DATABASE_USER: ${DB_USER}
REDIS_HOST: redis
API_KEY_FILE: /run/secrets/api_key
JWT_SECRET_FILE: /run/secrets/jwt_secret
secrets:
- api_key
- jwt_secret
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- production
expose: