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
Aligns main with the complete application codebase (cicd branch). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
457 lines
18 KiB
YAML
457 lines
18 KiB
YAML
version: '3.8'
|
|
|
|
# Xpeditis - Stack PRODUCTION
|
|
# Portainer Stack avec Traefik reverse proxy
|
|
# Domaines: xpeditis.com (frontend) | api.xpeditis.com (backend)
|
|
|
|
services:
|
|
# PostgreSQL Database
|
|
postgres-prod:
|
|
image: postgres:15-alpine
|
|
container_name: xpeditis-postgres-prod
|
|
restart: always
|
|
environment:
|
|
POSTGRES_DB: ${POSTGRES_DB:-xpeditis_prod}
|
|
POSTGRES_USER: ${POSTGRES_USER:-xpeditis}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?error}
|
|
PGDATA: /var/lib/postgresql/data/pgdata
|
|
volumes:
|
|
- postgres_data_prod:/var/lib/postgresql/data
|
|
- postgres_backups_prod:/backups
|
|
networks:
|
|
- xpeditis_internal_prod
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-xpeditis}"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '2'
|
|
memory: 4G
|
|
reservations:
|
|
cpus: '1'
|
|
memory: 2G
|
|
|
|
# Redis Cache
|
|
redis-prod:
|
|
image: redis:7-alpine
|
|
container_name: xpeditis-redis-prod
|
|
restart: always
|
|
command: redis-server --requirepass ${REDIS_PASSWORD:?error} --maxmemory 1gb --maxmemory-policy allkeys-lru --appendonly yes
|
|
volumes:
|
|
- redis_data_prod:/data
|
|
networks:
|
|
- xpeditis_internal_prod
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
|
interval: 10s
|
|
timeout: 3s
|
|
retries: 5
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '1'
|
|
memory: 1.5G
|
|
reservations:
|
|
cpus: '0.5'
|
|
memory: 1G
|
|
|
|
# Backend API (NestJS) - Instance 1
|
|
backend-prod-1:
|
|
image: ${DOCKER_REGISTRY:-docker.io}/${BACKEND_IMAGE:-xpeditis/backend}:${BACKEND_TAG:-latest}
|
|
container_name: xpeditis-backend-prod-1
|
|
restart: always
|
|
depends_on:
|
|
postgres-prod:
|
|
condition: service_healthy
|
|
redis-prod:
|
|
condition: service_healthy
|
|
environment:
|
|
# Application
|
|
NODE_ENV: production
|
|
PORT: 4000
|
|
INSTANCE_ID: backend-prod-1
|
|
|
|
# Database
|
|
DATABASE_HOST: postgres-prod
|
|
DATABASE_PORT: 5432
|
|
DATABASE_NAME: ${POSTGRES_DB:-xpeditis_prod}
|
|
DATABASE_USER: ${POSTGRES_USER:-xpeditis}
|
|
DATABASE_PASSWORD: ${POSTGRES_PASSWORD:?error}
|
|
DATABASE_SYNC: "false"
|
|
DATABASE_LOGGING: "false"
|
|
DATABASE_POOL_MIN: 10
|
|
DATABASE_POOL_MAX: 50
|
|
|
|
# Redis
|
|
REDIS_HOST: redis-prod
|
|
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://xpeditis.com,https://www.xpeditis.com
|
|
|
|
# Sentry (Monitoring)
|
|
SENTRY_DSN: ${SENTRY_DSN:?error}
|
|
SENTRY_ENVIRONMENT: production
|
|
SENTRY_TRACES_SAMPLE_RATE: 0.1
|
|
SENTRY_PROFILES_SAMPLE_RATE: 0.05
|
|
|
|
# AWS S3
|
|
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-prod-documents}
|
|
S3_BUCKET_UPLOADS: ${S3_BUCKET_UPLOADS:-xpeditis-prod-uploads}
|
|
|
|
# Email (AWS SES)
|
|
EMAIL_SERVICE: ses
|
|
EMAIL_FROM: ${EMAIL_FROM:-noreply@xpeditis.com}
|
|
EMAIL_FROM_NAME: Xpeditis
|
|
AWS_SES_REGION: ${AWS_SES_REGION:-eu-west-1}
|
|
|
|
# Carrier APIs (Production)
|
|
MAERSK_API_URL: ${MAERSK_API_URL:-https://api.maersk.com}
|
|
MAERSK_API_KEY: ${MAERSK_API_KEY:?error}
|
|
MSC_API_URL: ${MSC_API_URL:-}
|
|
MSC_API_KEY: ${MSC_API_KEY:-}
|
|
CMA_CGM_API_URL: ${CMA_CGM_API_URL:-}
|
|
CMA_CGM_API_KEY: ${CMA_CGM_API_KEY:-}
|
|
|
|
# Security
|
|
RATE_LIMIT_GLOBAL: 100
|
|
RATE_LIMIT_AUTH: 5
|
|
RATE_LIMIT_SEARCH: 30
|
|
RATE_LIMIT_BOOKING: 20
|
|
|
|
volumes:
|
|
- backend_logs_prod:/app/logs
|
|
networks:
|
|
- xpeditis_internal_prod
|
|
- traefik_network
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=traefik_network"
|
|
|
|
# HTTPS Route
|
|
- "traefik.http.routers.xpeditis-backend-prod.rule=Host(`api.xpeditis.com`)"
|
|
- "traefik.http.routers.xpeditis-backend-prod.entrypoints=websecure"
|
|
- "traefik.http.routers.xpeditis-backend-prod.tls=true"
|
|
- "traefik.http.routers.xpeditis-backend-prod.tls.certresolver=letsencrypt"
|
|
- "traefik.http.routers.xpeditis-backend-prod.priority=200"
|
|
- "traefik.http.services.xpeditis-backend-prod.loadbalancer.server.port=4000"
|
|
- "traefik.http.routers.xpeditis-backend-prod.middlewares=xpeditis-backend-prod-headers,xpeditis-backend-prod-security,xpeditis-backend-prod-ratelimit"
|
|
|
|
# HTTP → HTTPS Redirect
|
|
- "traefik.http.routers.xpeditis-backend-prod-http.rule=Host(`api.xpeditis.com`)"
|
|
- "traefik.http.routers.xpeditis-backend-prod-http.entrypoints=web"
|
|
- "traefik.http.routers.xpeditis-backend-prod-http.priority=200"
|
|
- "traefik.http.routers.xpeditis-backend-prod-http.middlewares=xpeditis-backend-prod-redirect"
|
|
- "traefik.http.routers.xpeditis-backend-prod-http.service=xpeditis-backend-prod"
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-redirect.redirectscheme.scheme=https"
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-redirect.redirectscheme.permanent=true"
|
|
|
|
# Middleware Headers
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-headers.headers.customRequestHeaders.X-Forwarded-Proto=https"
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-headers.headers.customRequestHeaders.X-Forwarded-For="
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-headers.headers.customRequestHeaders.X-Real-IP="
|
|
|
|
# Security Headers (Strict Production)
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-security.headers.frameDeny=true"
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-security.headers.contentTypeNosniff=true"
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-security.headers.browserXssFilter=true"
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-security.headers.stsSeconds=63072000"
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-security.headers.stsIncludeSubdomains=true"
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-security.headers.stsPreload=true"
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-security.headers.forceSTSHeader=true"
|
|
|
|
# Rate Limiting (Stricter in Production)
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-ratelimit.ratelimit.average=50"
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-ratelimit.ratelimit.burst=100"
|
|
- "traefik.http.middlewares.xpeditis-backend-prod-ratelimit.ratelimit.period=1m"
|
|
|
|
# Health Check
|
|
- "traefik.http.services.xpeditis-backend-prod.loadbalancer.healthcheck.path=/health"
|
|
- "traefik.http.services.xpeditis-backend-prod.loadbalancer.healthcheck.interval=30s"
|
|
- "traefik.http.services.xpeditis-backend-prod.loadbalancer.healthcheck.timeout=5s"
|
|
|
|
# Load Balancing (Sticky Sessions)
|
|
- "traefik.http.services.xpeditis-backend-prod.loadbalancer.sticky.cookie=true"
|
|
- "traefik.http.services.xpeditis-backend-prod.loadbalancer.sticky.cookie.name=xpeditis_backend_route"
|
|
- "traefik.http.services.xpeditis-backend-prod.loadbalancer.sticky.cookie.secure=true"
|
|
- "traefik.http.services.xpeditis-backend-prod.loadbalancer.sticky.cookie.httpOnly=true"
|
|
|
|
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: 60s
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '2'
|
|
memory: 2G
|
|
reservations:
|
|
cpus: '1'
|
|
memory: 1G
|
|
|
|
# Backend API (NestJS) - Instance 2 (High Availability)
|
|
backend-prod-2:
|
|
image: ${DOCKER_REGISTRY:-docker.io}/${BACKEND_IMAGE:-xpeditis/backend}:${BACKEND_TAG:-latest}
|
|
container_name: xpeditis-backend-prod-2
|
|
restart: always
|
|
depends_on:
|
|
postgres-prod:
|
|
condition: service_healthy
|
|
redis-prod:
|
|
condition: service_healthy
|
|
environment:
|
|
# Application
|
|
NODE_ENV: production
|
|
PORT: 4000
|
|
INSTANCE_ID: backend-prod-2
|
|
|
|
# Database
|
|
DATABASE_HOST: postgres-prod
|
|
DATABASE_PORT: 5432
|
|
DATABASE_NAME: ${POSTGRES_DB:-xpeditis_prod}
|
|
DATABASE_USER: ${POSTGRES_USER:-xpeditis}
|
|
DATABASE_PASSWORD: ${POSTGRES_PASSWORD:?error}
|
|
DATABASE_SYNC: "false"
|
|
DATABASE_LOGGING: "false"
|
|
DATABASE_POOL_MIN: 10
|
|
DATABASE_POOL_MAX: 50
|
|
|
|
# Redis
|
|
REDIS_HOST: redis-prod
|
|
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://xpeditis.com,https://www.xpeditis.com
|
|
|
|
# Sentry (Monitoring)
|
|
SENTRY_DSN: ${SENTRY_DSN:?error}
|
|
SENTRY_ENVIRONMENT: production
|
|
SENTRY_TRACES_SAMPLE_RATE: 0.1
|
|
SENTRY_PROFILES_SAMPLE_RATE: 0.05
|
|
|
|
# AWS S3
|
|
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-prod-documents}
|
|
S3_BUCKET_UPLOADS: ${S3_BUCKET_UPLOADS:-xpeditis-prod-uploads}
|
|
|
|
# Email (AWS SES)
|
|
EMAIL_SERVICE: ses
|
|
EMAIL_FROM: ${EMAIL_FROM:-noreply@xpeditis.com}
|
|
EMAIL_FROM_NAME: Xpeditis
|
|
AWS_SES_REGION: ${AWS_SES_REGION:-eu-west-1}
|
|
|
|
# Carrier APIs (Production)
|
|
MAERSK_API_URL: ${MAERSK_API_URL:-https://api.maersk.com}
|
|
MAERSK_API_KEY: ${MAERSK_API_KEY:?error}
|
|
MSC_API_URL: ${MSC_API_URL:-}
|
|
MSC_API_KEY: ${MSC_API_KEY:-}
|
|
CMA_CGM_API_URL: ${CMA_CGM_API_URL:-}
|
|
CMA_CGM_API_KEY: ${CMA_CGM_API_KEY:-}
|
|
|
|
# Security
|
|
RATE_LIMIT_GLOBAL: 100
|
|
RATE_LIMIT_AUTH: 5
|
|
RATE_LIMIT_SEARCH: 30
|
|
RATE_LIMIT_BOOKING: 20
|
|
|
|
volumes:
|
|
- backend_logs_prod:/app/logs
|
|
networks:
|
|
- xpeditis_internal_prod
|
|
- traefik_network
|
|
labels:
|
|
# Same Traefik labels as backend-prod-1 (load balanced)
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=traefik_network"
|
|
- "traefik.http.routers.xpeditis-backend-prod.rule=Host(`api.xpeditis.com`)"
|
|
- "traefik.http.services.xpeditis-backend-prod.loadbalancer.server.port=4000"
|
|
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: 60s
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '2'
|
|
memory: 2G
|
|
reservations:
|
|
cpus: '1'
|
|
memory: 1G
|
|
|
|
# Frontend (Next.js) - Instance 1
|
|
frontend-prod-1:
|
|
image: ${DOCKER_REGISTRY:-docker.io}/${FRONTEND_IMAGE:-xpeditis/frontend}:${FRONTEND_TAG:-latest}
|
|
container_name: xpeditis-frontend-prod-1
|
|
restart: always
|
|
depends_on:
|
|
- backend-prod-1
|
|
- backend-prod-2
|
|
environment:
|
|
NODE_ENV: production
|
|
NEXT_PUBLIC_API_URL: https://api.xpeditis.com
|
|
NEXT_PUBLIC_APP_URL: https://xpeditis.com
|
|
NEXT_PUBLIC_SENTRY_DSN: ${NEXT_PUBLIC_SENTRY_DSN:?error}
|
|
NEXT_PUBLIC_SENTRY_ENVIRONMENT: production
|
|
NEXT_PUBLIC_GA_MEASUREMENT_ID: ${NEXT_PUBLIC_GA_MEASUREMENT_ID:?error}
|
|
|
|
# Backend API for SSR (internal load balanced)
|
|
API_URL: http://backend-prod-1:4000
|
|
|
|
networks:
|
|
- xpeditis_internal_prod
|
|
- traefik_network
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=traefik_network"
|
|
|
|
# HTTPS Route
|
|
- "traefik.http.routers.xpeditis-frontend-prod.rule=Host(`xpeditis.com`) || Host(`www.xpeditis.com`)"
|
|
- "traefik.http.routers.xpeditis-frontend-prod.entrypoints=websecure"
|
|
- "traefik.http.routers.xpeditis-frontend-prod.tls=true"
|
|
- "traefik.http.routers.xpeditis-frontend-prod.tls.certresolver=letsencrypt"
|
|
- "traefik.http.routers.xpeditis-frontend-prod.priority=200"
|
|
- "traefik.http.services.xpeditis-frontend-prod.loadbalancer.server.port=3000"
|
|
- "traefik.http.routers.xpeditis-frontend-prod.middlewares=xpeditis-frontend-prod-headers,xpeditis-frontend-prod-security,xpeditis-frontend-prod-compress,xpeditis-frontend-prod-www-redirect"
|
|
|
|
# HTTP → HTTPS Redirect
|
|
- "traefik.http.routers.xpeditis-frontend-prod-http.rule=Host(`xpeditis.com`) || Host(`www.xpeditis.com`)"
|
|
- "traefik.http.routers.xpeditis-frontend-prod-http.entrypoints=web"
|
|
- "traefik.http.routers.xpeditis-frontend-prod-http.priority=200"
|
|
- "traefik.http.routers.xpeditis-frontend-prod-http.middlewares=xpeditis-frontend-prod-redirect"
|
|
- "traefik.http.routers.xpeditis-frontend-prod-http.service=xpeditis-frontend-prod"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-redirect.redirectscheme.scheme=https"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-redirect.redirectscheme.permanent=true"
|
|
|
|
# WWW → non-WWW Redirect
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-www-redirect.redirectregex.regex=^https://www\\.(.+)"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-www-redirect.redirectregex.replacement=https://$${1}"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-www-redirect.redirectregex.permanent=true"
|
|
|
|
# Middleware Headers
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-headers.headers.customRequestHeaders.X-Forwarded-Proto=https"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-headers.headers.customRequestHeaders.X-Forwarded-For="
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-headers.headers.customRequestHeaders.X-Real-IP="
|
|
|
|
# Security Headers (Strict Production)
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-security.headers.frameDeny=true"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-security.headers.contentTypeNosniff=true"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-security.headers.browserXssFilter=true"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-security.headers.stsSeconds=63072000"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-security.headers.stsIncludeSubdomains=true"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-security.headers.stsPreload=true"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-security.headers.forceSTSHeader=true"
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-security.headers.contentSecurityPolicy=default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.xpeditis.com;"
|
|
|
|
# Compression
|
|
- "traefik.http.middlewares.xpeditis-frontend-prod-compress.compress=true"
|
|
|
|
# Health Check
|
|
- "traefik.http.services.xpeditis-frontend-prod.loadbalancer.healthcheck.path=/api/health"
|
|
- "traefik.http.services.xpeditis-frontend-prod.loadbalancer.healthcheck.interval=30s"
|
|
- "traefik.http.services.xpeditis-frontend-prod.loadbalancer.healthcheck.timeout=5s"
|
|
|
|
# Load Balancing (Sticky Sessions)
|
|
- "traefik.http.services.xpeditis-frontend-prod.loadbalancer.sticky.cookie=true"
|
|
- "traefik.http.services.xpeditis-frontend-prod.loadbalancer.sticky.cookie.name=xpeditis_frontend_route"
|
|
- "traefik.http.services.xpeditis-frontend-prod.loadbalancer.sticky.cookie.secure=true"
|
|
- "traefik.http.services.xpeditis-frontend-prod.loadbalancer.sticky.cookie.httpOnly=true"
|
|
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 60s
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '2'
|
|
memory: 2G
|
|
reservations:
|
|
cpus: '1'
|
|
memory: 1G
|
|
|
|
# Frontend (Next.js) - Instance 2 (High Availability)
|
|
frontend-prod-2:
|
|
image: ${DOCKER_REGISTRY:-docker.io}/${FRONTEND_IMAGE:-xpeditis/frontend}:${FRONTEND_TAG:-latest}
|
|
container_name: xpeditis-frontend-prod-2
|
|
restart: always
|
|
depends_on:
|
|
- backend-prod-1
|
|
- backend-prod-2
|
|
environment:
|
|
NODE_ENV: production
|
|
NEXT_PUBLIC_API_URL: https://api.xpeditis.com
|
|
NEXT_PUBLIC_APP_URL: https://xpeditis.com
|
|
NEXT_PUBLIC_SENTRY_DSN: ${NEXT_PUBLIC_SENTRY_DSN:?error}
|
|
NEXT_PUBLIC_SENTRY_ENVIRONMENT: production
|
|
NEXT_PUBLIC_GA_MEASUREMENT_ID: ${NEXT_PUBLIC_GA_MEASUREMENT_ID:?error}
|
|
|
|
# Backend API for SSR (internal load balanced)
|
|
API_URL: http://backend-prod-2:4000
|
|
|
|
networks:
|
|
- xpeditis_internal_prod
|
|
- traefik_network
|
|
labels:
|
|
# Same Traefik labels as frontend-prod-1 (load balanced)
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=traefik_network"
|
|
- "traefik.http.routers.xpeditis-frontend-prod.rule=Host(`xpeditis.com`) || Host(`www.xpeditis.com`)"
|
|
- "traefik.http.services.xpeditis-frontend-prod.loadbalancer.server.port=3000"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 60s
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '2'
|
|
memory: 2G
|
|
reservations:
|
|
cpus: '1'
|
|
memory: 1G
|
|
|
|
networks:
|
|
xpeditis_internal_prod:
|
|
driver: bridge
|
|
name: xpeditis_internal_prod
|
|
traefik_network:
|
|
external: true
|
|
|
|
volumes:
|
|
postgres_data_prod:
|
|
name: xpeditis_postgres_data_prod
|
|
postgres_backups_prod:
|
|
name: xpeditis_postgres_backups_prod
|
|
redis_data_prod:
|
|
name: xpeditis_redis_data_prod
|
|
backend_logs_prod:
|
|
name: xpeditis_backend_logs_prod
|