Files
ERP/docker-compose.prod.yml
T
whqreza 887a3af84f refactor(monitoring): move grafana into the main compose stack
Owner request: one 'docker compose up -d' should bring the whole system up —
no separate monitoring project to start for the dashboards.

- docker-compose.yml: grafana service (procurement-grafana, loopback :3001,
  default+shared networks, obs datasource env) + grafana_data volume;
  provisioning and dashboard JSONs stay in ./monitoring/grafana/ (no file
  moves, provisioning unchanged)
- docker-compose.prod.yml: GRAFANA_ADMIN_PASSWORD forced via the existing
  ${VAR:?} pattern (local keeps a lenient default, prod refuses defaults)
- monitoring/docker-compose.monitoring.yml: grafana service + volume removed
  (no name/port conflict); glitchtip/prometheus/loki stay server-side as before
- docs updated: grafana restarts on app deploys are harmless (settings live
  in the volume); SSH-tunnel posture unchanged

Verified locally: both compose files validate; 'docker compose up -d' starts
all 10 services incl. procurement-grafana; BotPostgres datasource health OK;
all 3 dashboards provisioned in the fresh volume; monitoring compose no
longer contains grafana; old monitoring-grafana container removed; port
still loopback-only.
2026-10-27 01:43:31 +03:30

106 lines
5.4 KiB
YAML

# ============================================================================
# Production overlay — real domains + HTTPS (Let's Encrypt) + HTTP→HTTPS
# redirect + security middlewares + required secrets.
#
# All credentials come from the git-ignored .env NEXT TO THE COMPOSE FILES
# (compose loads it automatically — keep it on the VPS, never in git):
#
# cp .env.example .env # then fill in STRONG values
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
#
# Requirements: DNS records for every host pointing at the server, ports
# 80 + 443 open (ACME http-challenge uses port 80). The api's issuer is
# derived here from AUTH_HOST (see docs/casdoor-auth.md).
#
# Any `${VAR:?}` below refuses to boot when the variable is missing/empty —
# that is deliberate: production must not run on defaults.
# ============================================================================
services:
traefik:
environment:
# Certificates persist across restarts (acme.json in the traefik_certs volume).
TRAEFIK_ACME_EMAIL: ${TRAEFIK_ACME_EMAIL:?set TRAEFIK_ACME_EMAIL in .env}
postgres:
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
grafana:
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?set GRAFANA_ADMIN_PASSWORD in .env}
minio:
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?set MINIO_ROOT_USER in .env}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD in .env}
labels:
- traefik.http.routers.minio-api.rule=Host(`${S3_HOST:?set S3_HOST}`)
- traefik.http.routers.minio-api.entrypoints=websecure
- traefik.http.routers.minio-api.tls=true
- traefik.http.routers.minio-api.tls.certresolver=letsencrypt
- traefik.http.routers.minio-api.middlewares=secure-headers@file
- traefik.http.routers.minio-api-redir.rule=Host(`${S3_HOST:?set S3_HOST}`)
- traefik.http.routers.minio-api-redir.entrypoints=web
- traefik.http.routers.minio-api-redir.middlewares=redirect-https@file
- traefik.http.routers.minio-console.rule=Host(`${STORAGE_HOST:?set STORAGE_HOST}`)
- traefik.http.routers.minio-console.entrypoints=websecure
- traefik.http.routers.minio-console.tls=true
- traefik.http.routers.minio-console.tls.certresolver=letsencrypt
- traefik.http.routers.minio-console.middlewares=secure-headers@file
- traefik.http.routers.minio-console-redir.rule=Host(`${STORAGE_HOST:?set STORAGE_HOST}`)
- traefik.http.routers.minio-console-redir.entrypoints=web
- traefik.http.routers.minio-console-redir.middlewares=redirect-https@file
api:
ports: [] # production: only reachable through Traefik
environment:
PROCUREMENT_DATABASE_URL: postgresql+asyncpg://postgres:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@postgres:5432/procurement
PROCUREMENT_MINIO_ACCESS_KEY: ${MINIO_ROOT_USER:?set MINIO_ROOT_USER in .env}
PROCUREMENT_MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD in .env}
PROCUREMENT_CASDOOR_CLIENT_SECRET: ${CASDOOR_CLIENT_SECRET:?set CASDOOR_CLIENT_SECRET in .env}
PROCUREMENT_CASDOOR_ADMIN_PASSWORD: ${CASDOOR_ADMIN_PASSWORD:?set CASDOOR_ADMIN_PASSWORD in .env}
# Issuer must equal the browser-facing Casdoor origin (its discovery doc).
PROCUREMENT_CASDOOR_ISSUER: https://${AUTH_HOST:?set AUTH_HOST}
PROCUREMENT_CORS_ORIGINS: ${CORS_ORIGINS:?set CORS_ORIGINS in .env}
PROCUREMENT_ENVIRONMENT: prod
PROCUREMENT_DEV_LOGIN_ENABLED: "false"
labels:
- traefik.http.routers.api.rule=Host(`${API_HOST:?set API_HOST}`)
- traefik.http.routers.api.entrypoints=websecure
- traefik.http.routers.api.tls=true
- traefik.http.routers.api.tls.certresolver=letsencrypt
- traefik.http.routers.api.middlewares=secure-headers@file,api-ratelimit@file,api-bodylimit@file
- traefik.http.routers.api-redir.rule=Host(`${API_HOST:?set API_HOST}`)
- traefik.http.routers.api-redir.entrypoints=web
- traefik.http.routers.api-redir.middlewares=redirect-https@file
casdoor:
ports: [] # production: only reachable through Traefik (auth host)
environment:
# Casdoor must know its public origin so tokens carry the right iss.
origin: "https://${AUTH_HOST:?set AUTH_HOST}"
labels:
- traefik.http.routers.casdoor.rule=Host(`${AUTH_HOST:?set AUTH_HOST}`)
- traefik.http.routers.casdoor.entrypoints=websecure
- traefik.http.routers.casdoor.tls=true
- traefik.http.routers.casdoor.tls.certresolver=letsencrypt
- traefik.http.routers.casdoor.middlewares=secure-headers@file
- traefik.http.routers.casdoor-redir.rule=Host(`${AUTH_HOST:?set AUTH_HOST}`)
- traefik.http.routers.casdoor-redir.entrypoints=web
- traefik.http.routers.casdoor-redir.middlewares=redirect-https@file
frontend:
build:
args:
DEV_LOGIN_ENABLED: "false"
labels:
- traefik.http.routers.frontend.rule=Host(`${APP_HOST:?set APP_HOST}`)
- traefik.http.routers.frontend.entrypoints=websecure
- traefik.http.routers.frontend.tls=true
- traefik.http.routers.frontend.tls.certresolver=letsencrypt
- traefik.http.routers.frontend.middlewares=secure-headers@file
- traefik.http.routers.frontend-redir.rule=Host(`${APP_HOST:?set APP_HOST}`)
- traefik.http.routers.frontend-redir.entrypoints=web
- traefik.http.routers.frontend-redir.middlewares=redirect-https@file