# Production stack: one per customer. # Requires: # - COMPOSE_PROJECT_NAME=abc0001 (LOWERCASE; Compose v2 rejects uppercase. Use the # lowercase form of the customer ID — abc0001 for ABC0001.) # - CUSTOMER_HOST=abc0001.portal.example.com (Traefik routes by this Host) # - POSTGRES_PASSWORD, GRAFANA_ADMIN_PASSWORD set in .env # - An external Docker network `traefik-public` created by your Traefik stack # # Run with: # docker compose -f docker-compose.prod.yml --env-file .env up -d services: portal: build: . image: tau-acuvim-portal:latest container_name: ${COMPOSE_PROJECT_NAME}_portal restart: unless-stopped environment: - ASPNETCORE_ENVIRONMENT=Production - Application__PublicUrl=https://${CUSTOMER_HOST} - Database__ConnectionString=Host=timescaledb;Port=5432;Database=${POSTGRES_DB:-power_monitoring};Username=${POSTGRES_USER:-power_user};Password=${POSTGRES_PASSWORD} - Database__AutoProvisionLocalTimescaleDb=false - Authentication__DefaultAdminEmail=${Authentication__DefaultAdminEmail} - Authentication__DefaultAdminPassword=${Authentication__DefaultAdminPassword} - Grafana__BaseUrl=https://${CUSTOMER_HOST}${Grafana__EmbedPathPrefix:-/grafana} - Grafana__InternalUrl=http://grafana:3000 - Grafana__EmbedPathPrefix=${Grafana__EmbedPathPrefix:-/grafana} depends_on: timescaledb: condition: service_healthy volumes: - portal-keys:/data/keys - portal-branding:/data/branding networks: - default - traefik-public labels: - "traefik.enable=true" - "traefik.docker.network=traefik-public" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-portal.rule=Host(`${CUSTOMER_HOST}`)" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-portal.entrypoints=websecure" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-portal.tls=true" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-portal.tls.certresolver=le" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-portal.priority=10" - "traefik.http.services.${COMPOSE_PROJECT_NAME}-portal.loadbalancer.server.port=8080" timescaledb: image: timescale/timescaledb:2.17.2-pg16 container_name: ${COMPOSE_PROJECT_NAME}_timescale restart: unless-stopped environment: - POSTGRES_DB=${POSTGRES_DB:-power_monitoring} - POSTGRES_USER=${POSTGRES_USER:-power_user} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} volumes: - timescale-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-power_user} -d ${POSTGRES_DB:-power_monitoring}"] interval: 10s timeout: 5s retries: 10 grafana: image: grafana/grafana:11.4.0 container_name: ${COMPOSE_PROJECT_NAME}_grafana restart: unless-stopped environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD} - GF_SECURITY_ALLOW_EMBEDDING=true - GF_SERVER_ROOT_URL=https://${CUSTOMER_HOST}${Grafana__EmbedPathPrefix:-/grafana} - GF_SERVER_SERVE_FROM_SUB_PATH=true # PROD AUTH IS NOT WIRED YET (Phase 9 risk). # Anonymous is OFF so dashboards do NOT serve until you choose: # (a) Traefik forwardAuth middleware → /api/auth/check # (b) Grafana auth.proxy (GF_AUTH_PROXY_*) with X-WEBAUTH-USER from portal/Traefik # (c) Service-account API key + portal-minted render tokens # See README "Embedding Grafana — production auth options". - GF_AUTH_ANONYMOUS_ENABLED=false - GF_USERS_ALLOW_SIGN_UP=false - POSTGRES_DB=${POSTGRES_DB:-power_monitoring} - POSTGRES_USER=${POSTGRES_USER:-power_user} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} volumes: - grafana-data:/var/lib/grafana - ./grafana/provisioning:/etc/grafana/provisioning:ro - ./grafana/dashboards:/var/lib/grafana/dashboards:ro depends_on: timescaledb: condition: service_healthy networks: - default - traefik-public labels: - "traefik.enable=true" - "traefik.docker.network=traefik-public" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-grafana.rule=Host(`${CUSTOMER_HOST}`) && PathPrefix(`${Grafana__EmbedPathPrefix:-/grafana}`)" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-grafana.entrypoints=websecure" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-grafana.tls=true" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-grafana.tls.certresolver=le" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-grafana.priority=20" - "traefik.http.services.${COMPOSE_PROJECT_NAME}-grafana.loadbalancer.server.port=3000" volumes: portal-keys: portal-branding: timescale-data: grafana-data: networks: traefik-public: external: true