Some checks failed
Dev CI / Unit Tests (${{ matrix.app }}) (backend) (push) Blocked by required conditions
Dev CI / Unit Tests (${{ matrix.app }}) (frontend) (push) Blocked by required conditions
Dev CI / Notify Failure (push) Blocked by required conditions
Dev CI / Quality (${{ matrix.app }}) (backend) (push) Has been cancelled
Dev CI / Quality (${{ matrix.app }}) (frontend) (push) Has been cancelled
Aligns dev with the complete application codebase (cicd branch). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
254 lines
11 KiB
YAML
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
|