Files
Classeo/compose.yaml
Mathias STRASSER 9ccad77bf0 feat: Messaging asynchrone fiable avec retry, dead-letter et métriques
Les événements métier (emails d'invitation, reset password, activation)
bloquaient la réponse API en étant traités de manière synchrone. Ce commit
route ces événements vers un transport AMQP asynchrone avec un worker
dédié, garantissant des réponses API rapides et une gestion robuste des
échecs.

Le retry utilise une stratégie Fibonacci (1s, 1s, 2s, 3s, 5s, 8s, 13s)
qui offre un bon compromis entre réactivité et protection des services
externes. Les messages qui épuisent leurs tentatives arrivent dans une
dead-letter queue Doctrine avec alerte email à l'admin.

La commande console CreateTestActivationTokenCommand détecte désormais
les comptes déjà actifs et génère un token de réinitialisation de mot
de passe au lieu d'un token d'activation, évitant une erreur bloquante
lors de la ré-invitation par un admin.
2026-02-08 21:38:20 +01:00

252 lines
8.3 KiB
YAML

services:
# =============================================================================
# BACKEND API - PHP 8.5 + FrankenPHP
# =============================================================================
php:
build:
context: ./backend
dockerfile: Dockerfile
target: dev
container_name: classeo_php
# FrankenPHP charge les variables d'environnement système AVANT que Symfony
# ne parse le fichier .env. Sans env_file, les variables du .env ne seraient
# pas disponibles au démarrage de FrankenPHP.
# Avantage : une seule source de vérité (.env), pas de duplication.
# Note : les variables dans 'environment:' ci-dessous écrasent celles du .env
env_file:
- ./backend/.env
environment:
# Overrides pour Docker : les hostnames des services utilisent les noms
# des containers (db, redis, rabbitmq...) au lieu de localhost
# APP_ENV peut être overridé en CI pour désactiver le rate limiting (test)
APP_ENV: ${APP_ENV:-dev}
DATABASE_URL: postgresql://classeo:classeo@db:5432/classeo_master?serverVersion=18&charset=utf8
REDIS_URL: redis://redis:6379
MESSENGER_TRANSPORT_DSN: amqp://guest:guest@rabbitmq:5672/%2f/messages
MERCURE_URL: http://mercure/.well-known/mercure
MEILISEARCH_URL: http://meilisearch:7700
MAILER_DSN: ${MAILER_DSN:-smtp://mailpit:1025}
ports:
- "18000:8000" # Port externe 18000 pour eviter conflit
volumes:
- ./backend:/app:cached
- caddy_data:/data
- caddy_config:/config
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
rabbitmq:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/docs"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
# =============================================================================
# FRONTEND - SvelteKit + Node.js
# =============================================================================
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
target: dev
container_name: classeo_frontend
environment:
# URL de fallback, sera remplacée dynamiquement par le hostname en multi-tenant
PUBLIC_API_URL: http://localhost:18000/api
PUBLIC_API_PORT: "18000"
PUBLIC_BASE_DOMAIN: classeo.local
PUBLIC_MERCURE_URL: http://localhost:3000/.well-known/mercure
ports:
- "5174:5173" # Port externe 5174 pour eviter conflit
volumes:
- ./frontend:/app:cached
- frontend_node_modules:/app/node_modules
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5173/"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
# =============================================================================
# DATABASE - PostgreSQL 18.1
# =============================================================================
db:
image: postgres:18.1-alpine
container_name: classeo_db
environment:
POSTGRES_DB: classeo_master
POSTGRES_USER: classeo
POSTGRES_PASSWORD: classeo
ports:
- "5433:5432" # Port externe 5433 pour eviter conflit avec PostgreSQL local
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U classeo -d classeo_master"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
# =============================================================================
# CACHE & SESSIONS - Redis 7.4
# =============================================================================
redis:
image: redis:7.4-alpine
container_name: classeo_redis
command: redis-server --appendonly yes
ports:
- "6380:6379" # Port externe 6380 pour eviter conflit avec Redis local
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
restart: unless-stopped
# =============================================================================
# MESSAGE QUEUE - RabbitMQ 4.2
# =============================================================================
rabbitmq:
image: rabbitmq:4.2-management-alpine
container_name: classeo_rabbitmq
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
ports:
- "5672:5672"
- "15672:15672"
volumes:
- rabbitmq_data:/var/lib/rabbitmq
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
restart: unless-stopped
# =============================================================================
# REAL-TIME SSE - Mercure
# =============================================================================
mercure:
image: dunglas/mercure:latest
container_name: classeo_mercure
environment:
MERCURE_PUBLISHER_JWT_KEY: "mercure_publisher_secret_change_me_in_production"
MERCURE_SUBSCRIBER_JWT_KEY: "mercure_subscriber_secret_change_me_in_production"
SERVER_NAME: ":80"
MERCURE_EXTRA_DIRECTIVES: |
cors_origins http://localhost:5174
anonymous
ports:
- "3000:80"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost/.well-known/mercure"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
# =============================================================================
# FULL-TEXT SEARCH - Meilisearch 1.12
# =============================================================================
meilisearch:
image: getmeili/meilisearch:v1.12
container_name: classeo_meilisearch
environment:
MEILI_MASTER_KEY: "masterKey"
MEILI_ENV: "development"
ports:
- "7700:7700"
volumes:
- meilisearch_data:/meili_data
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:7700/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
# =============================================================================
# ASYNC WORKER - Symfony Messenger
# =============================================================================
worker:
build:
context: ./backend
dockerfile: Dockerfile
target: dev
container_name: classeo_worker
env_file:
- ./backend/.env
environment:
APP_ENV: ${APP_ENV:-dev}
DATABASE_URL: postgresql://classeo:classeo@db:5432/classeo_master?serverVersion=18&charset=utf8
REDIS_URL: redis://redis:6379
MESSENGER_TRANSPORT_DSN: amqp://guest:guest@rabbitmq:5672/%2f/messages
MERCURE_URL: http://mercure/.well-known/mercure
MEILISEARCH_URL: http://meilisearch:7700
MAILER_DSN: ${MAILER_DSN:-smtp://mailpit:1025}
command: php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -vv
volumes:
- ./backend:/app:cached
depends_on:
rabbitmq:
condition: service_healthy
php:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "ps aux | grep 'messenger:consume' | grep -v grep"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
deploy:
resources:
limits:
memory: 256M
restart: unless-stopped
# =============================================================================
# EMAIL TESTING - Mailpit
# =============================================================================
mailpit:
image: axllent/mailpit:latest
container_name: classeo_mailpit
ports:
- "1025:1025"
- "8025:8025"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8025"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
restart: unless-stopped
# =============================================================================
# VOLUMES PERSISTANTS
# =============================================================================
volumes:
postgres_data:
redis_data:
rabbitmq_data:
meilisearch_data:
frontend_node_modules:
caddy_data:
caddy_config: