xpeditis2.0/docker/portainer-stack-staging.yml
David d65cb721b5
Some checks are pending
CD Production (Hetzner k3s) / Promote Images (preprod → prod) (push) Waiting to run
CD Production (Hetzner k3s) / Deploy to k3s (xpeditis-prod) (push) Blocked by required conditions
CD Production (Hetzner k3s) / Smoke Tests (push) Blocked by required conditions
CD Production (Hetzner k3s) / Deployment Summary (push) Blocked by required conditions
CD Production (Hetzner k3s) / Notify Success (push) Blocked by required conditions
CD Production (Hetzner k3s) / Notify Failure (push) Blocked by required conditions
chore: sync full codebase from cicd branch
Aligns main with the complete application codebase (cicd branch).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:56:44 +02:00

254 lines
11 KiB
YAML

version: '3.8'
# Xpeditis - Stack STAGING/PREPROD
# Portainer Stack avec Traefik reverse proxy
# Domaines: staging.xpeditis.com (frontend) | api-staging.xpeditis.com (backend)
services:
# PostgreSQL Database
postgres-staging:
image: postgres:15-alpine
container_name: xpeditis-postgres-staging
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-xpeditis_staging}
POSTGRES_USER: ${POSTGRES_USER:-xpeditis}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?error}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- postgres_data_staging:/var/lib/postgresql/data
networks:
- xpeditis_internal_staging
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-xpeditis}"]
interval: 10s
timeout: 5s
retries: 5
# Redis Cache
redis-staging:
image: redis:7-alpine
container_name: xpeditis-redis-staging
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD:?error} --maxmemory 512mb --maxmemory-policy allkeys-lru
volumes:
- redis_data_staging:/data
networks:
- xpeditis_internal_staging
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 10s
timeout: 3s
retries: 5
# Backend API (NestJS)
backend-staging:
image: ${DOCKER_REGISTRY:-docker.io}/${BACKEND_IMAGE:-xpeditis/backend}:${BACKEND_TAG:-staging-latest}
container_name: xpeditis-backend-staging
restart: unless-stopped
depends_on:
postgres-staging:
condition: service_healthy
redis-staging:
condition: service_healthy
environment:
# Application
NODE_ENV: staging
PORT: 4000
# Database
DATABASE_HOST: postgres-staging
DATABASE_PORT: 5432
DATABASE_NAME: ${POSTGRES_DB:-xpeditis_staging}
DATABASE_USER: ${POSTGRES_USER:-xpeditis}
DATABASE_PASSWORD: ${POSTGRES_PASSWORD:?error}
DATABASE_SYNC: "false"
DATABASE_LOGGING: "true"
# Redis
REDIS_HOST: redis-staging
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:?error}
# JWT
JWT_SECRET: ${JWT_SECRET:?error}
JWT_ACCESS_EXPIRATION: 15m
JWT_REFRESH_EXPIRATION: 7d
# CORS
CORS_ORIGIN: https://staging.xpeditis.com,http://localhost:3000
# Sentry (Monitoring)
SENTRY_DSN: ${SENTRY_DSN:-}
SENTRY_ENVIRONMENT: staging
SENTRY_TRACES_SAMPLE_RATE: 0.1
SENTRY_PROFILES_SAMPLE_RATE: 0.05
# AWS S3 (or MinIO)
AWS_REGION: ${AWS_REGION:-eu-west-3}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:?error}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:?error}
S3_BUCKET_DOCUMENTS: ${S3_BUCKET_DOCUMENTS:-xpeditis-staging-documents}
S3_BUCKET_UPLOADS: ${S3_BUCKET_UPLOADS:-xpeditis-staging-uploads}
# Email (AWS SES or SMTP)
EMAIL_SERVICE: ${EMAIL_SERVICE:-ses}
EMAIL_FROM: ${EMAIL_FROM:-noreply@staging.xpeditis.com}
EMAIL_FROM_NAME: Xpeditis Staging
AWS_SES_REGION: ${AWS_SES_REGION:-eu-west-1}
# Carrier APIs (Sandbox)
MAERSK_API_URL: ${MAERSK_API_URL_SANDBOX:-https://sandbox.api.maersk.com}
MAERSK_API_KEY: ${MAERSK_API_KEY_SANDBOX:-}
MSC_API_URL: ${MSC_API_URL_SANDBOX:-}
MSC_API_KEY: ${MSC_API_KEY_SANDBOX:-}
# Security
RATE_LIMIT_GLOBAL: 200
RATE_LIMIT_AUTH: 10
RATE_LIMIT_SEARCH: 50
RATE_LIMIT_BOOKING: 30
volumes:
- backend_logs_staging:/app/logs
networks:
- xpeditis_internal_staging
- traefik_network
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_network"
# HTTPS Route
- "traefik.http.routers.xpeditis-backend-staging.rule=Host(`api-staging.xpeditis.com`)"
- "traefik.http.routers.xpeditis-backend-staging.entrypoints=websecure"
- "traefik.http.routers.xpeditis-backend-staging.tls=true"
- "traefik.http.routers.xpeditis-backend-staging.tls.certresolver=letsencrypt"
- "traefik.http.routers.xpeditis-backend-staging.priority=100"
- "traefik.http.services.xpeditis-backend-staging.loadbalancer.server.port=4000"
- "traefik.http.routers.xpeditis-backend-staging.middlewares=xpeditis-backend-staging-headers,xpeditis-backend-staging-security"
# HTTP → HTTPS Redirect
- "traefik.http.routers.xpeditis-backend-staging-http.rule=Host(`api-staging.xpeditis.com`)"
- "traefik.http.routers.xpeditis-backend-staging-http.entrypoints=web"
- "traefik.http.routers.xpeditis-backend-staging-http.priority=100"
- "traefik.http.routers.xpeditis-backend-staging-http.middlewares=xpeditis-backend-staging-redirect"
- "traefik.http.routers.xpeditis-backend-staging-http.service=xpeditis-backend-staging"
- "traefik.http.middlewares.xpeditis-backend-staging-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.xpeditis-backend-staging-redirect.redirectscheme.permanent=true"
# Middleware Headers
- "traefik.http.middlewares.xpeditis-backend-staging-headers.headers.customRequestHeaders.X-Forwarded-Proto=https"
- "traefik.http.middlewares.xpeditis-backend-staging-headers.headers.customRequestHeaders.X-Forwarded-For="
- "traefik.http.middlewares.xpeditis-backend-staging-headers.headers.customRequestHeaders.X-Real-IP="
# Security Headers
- "traefik.http.middlewares.xpeditis-backend-staging-security.headers.frameDeny=true"
- "traefik.http.middlewares.xpeditis-backend-staging-security.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.xpeditis-backend-staging-security.headers.browserXssFilter=true"
- "traefik.http.middlewares.xpeditis-backend-staging-security.headers.stsSeconds=31536000"
- "traefik.http.middlewares.xpeditis-backend-staging-security.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.xpeditis-backend-staging-security.headers.stsPreload=true"
# Rate Limiting
- "traefik.http.middlewares.xpeditis-backend-staging-ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.xpeditis-backend-staging-ratelimit.ratelimit.burst=200"
# Health Check
- "traefik.http.services.xpeditis-backend-staging.loadbalancer.healthcheck.path=/health"
- "traefik.http.services.xpeditis-backend-staging.loadbalancer.healthcheck.interval=30s"
- "traefik.http.services.xpeditis-backend-staging.loadbalancer.healthcheck.timeout=5s"
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:4000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Frontend (Next.js)
frontend-staging:
image: ${DOCKER_REGISTRY:-docker.io}/${FRONTEND_IMAGE:-xpeditis/frontend}:${FRONTEND_TAG:-staging-latest}
container_name: xpeditis-frontend-staging
restart: unless-stopped
depends_on:
- backend-staging
environment:
NODE_ENV: staging
NEXT_PUBLIC_API_URL: https://api-staging.xpeditis.com
NEXT_PUBLIC_APP_URL: https://staging.xpeditis.com
NEXT_PUBLIC_SENTRY_DSN: ${NEXT_PUBLIC_SENTRY_DSN:-}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: staging
NEXT_PUBLIC_GA_MEASUREMENT_ID: ${NEXT_PUBLIC_GA_MEASUREMENT_ID:-}
# Backend API for SSR (internal)
API_URL: http://backend-staging:4000
networks:
- xpeditis_internal_staging
- traefik_network
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_network"
# HTTPS Route
- "traefik.http.routers.xpeditis-frontend-staging.rule=Host(`staging.xpeditis.com`)"
- "traefik.http.routers.xpeditis-frontend-staging.entrypoints=websecure"
- "traefik.http.routers.xpeditis-frontend-staging.tls=true"
- "traefik.http.routers.xpeditis-frontend-staging.tls.certresolver=letsencrypt"
- "traefik.http.routers.xpeditis-frontend-staging.priority=100"
- "traefik.http.services.xpeditis-frontend-staging.loadbalancer.server.port=3000"
- "traefik.http.routers.xpeditis-frontend-staging.middlewares=xpeditis-frontend-staging-headers,xpeditis-frontend-staging-security,xpeditis-frontend-staging-compress"
# HTTP → HTTPS Redirect
- "traefik.http.routers.xpeditis-frontend-staging-http.rule=Host(`staging.xpeditis.com`)"
- "traefik.http.routers.xpeditis-frontend-staging-http.entrypoints=web"
- "traefik.http.routers.xpeditis-frontend-staging-http.priority=100"
- "traefik.http.routers.xpeditis-frontend-staging-http.middlewares=xpeditis-frontend-staging-redirect"
- "traefik.http.routers.xpeditis-frontend-staging-http.service=xpeditis-frontend-staging"
- "traefik.http.middlewares.xpeditis-frontend-staging-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.xpeditis-frontend-staging-redirect.redirectscheme.permanent=true"
# Middleware Headers
- "traefik.http.middlewares.xpeditis-frontend-staging-headers.headers.customRequestHeaders.X-Forwarded-Proto=https"
- "traefik.http.middlewares.xpeditis-frontend-staging-headers.headers.customRequestHeaders.X-Forwarded-For="
- "traefik.http.middlewares.xpeditis-frontend-staging-headers.headers.customRequestHeaders.X-Real-IP="
# Security Headers
- "traefik.http.middlewares.xpeditis-frontend-staging-security.headers.frameDeny=true"
- "traefik.http.middlewares.xpeditis-frontend-staging-security.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.xpeditis-frontend-staging-security.headers.browserXssFilter=true"
- "traefik.http.middlewares.xpeditis-frontend-staging-security.headers.stsSeconds=31536000"
- "traefik.http.middlewares.xpeditis-frontend-staging-security.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.xpeditis-frontend-staging-security.headers.stsPreload=true"
- "traefik.http.middlewares.xpeditis-frontend-staging-security.headers.customResponseHeaders.X-Robots-Tag=noindex,nofollow"
# Compression
- "traefik.http.middlewares.xpeditis-frontend-staging-compress.compress=true"
# Health Check
- "traefik.http.services.xpeditis-frontend-staging.loadbalancer.healthcheck.path=/api/health"
- "traefik.http.services.xpeditis-frontend-staging.loadbalancer.healthcheck.interval=30s"
- "traefik.http.services.xpeditis-frontend-staging.loadbalancer.healthcheck.timeout=5s"
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
xpeditis_internal_staging:
driver: bridge
name: xpeditis_internal_staging
traefik_network:
external: true
volumes:
postgres_data_staging:
name: xpeditis_postgres_data_staging
redis_data_staging:
name: xpeditis_redis_data_staging
backend_logs_staging:
name: xpeditis_backend_logs_staging