GTI terminates TLS in its Nginx Proxy Manager (10.0.10.100), not on the Hub host, so the Hub must be reachable on the LAN — by the proxy only. - docker-compose.prod.yml passes FORWARDED_ALLOW_IPS (uvicorn then trusts X-Forwarded-For/-Proto from the proxy only: real client IPs in audit and session records, https scheme) and WEB_COOKIE_SECURE. Defaults keep the previous behaviour. - restrict_hub_port.sh + gti-fiscal-hub-port.service: Docker-published ports bypass UFW, so the source restriction lives in DOCKER-USER, matched on the conntrack original destination with --ctdir ORIGINAL, and is re-applied whenever docker.service starts (the chain is not persisted across reboots). - *.service forced to LF like *.sh; tests cover both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
74 lines
2.7 KiB
YAML
74 lines
2.7 KiB
YAML
# GTI Fiscal Hub — production compose (RC1). Only two containers: PostgreSQL + Hub. No Kubernetes/Redis/RabbitMQ.
|
|
#
|
|
# cd deploy/hub && cp .env.example .env && (edit .env) && docker compose -f docker-compose.prod.yml --env-file .env up -d --build
|
|
#
|
|
# PostgreSQL is on an internal network only: it publishes NO port and is not reachable from the LAN/internet.
|
|
# The Hub binds to HUB_BIND (default 127.0.0.1): put a TLS reverse proxy (HTTPS) in front of it for the Agents.
|
|
# Migrations run automatically at Hub start (Dockerfile CMD: alembic upgrade head).
|
|
name: gti-fiscal-hub
|
|
|
|
x-logging: &logging
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "5"
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: gti_fiscal
|
|
POSTGRES_USER: gti
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required (URL-safe, e.g. openssl rand -hex 24)}
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
- ${BACKUP_DIR:-./backups}:/backups
|
|
networks: [internal]
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U gti -d gti_fiscal"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 12
|
|
logging: *logging
|
|
|
|
fiscal-hub:
|
|
build:
|
|
context: ../..
|
|
dockerfile: central/Dockerfile
|
|
image: gti-fiscal-hub:${GTI_FISCAL_VERSION:-1.0.0-rc1}
|
|
restart: unless-stopped
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
environment:
|
|
APP_ENV: production
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
|
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
|
BASE_URL: ${BASE_URL:?BASE_URL is required (public HTTPS URL of the Hub)}
|
|
AGENT_ONLINE_THRESHOLD_SECONDS: ${AGENT_ONLINE_THRESHOLD_SECONDS:-120}
|
|
# Reverse proxy on another host: uvicorn only honours X-Forwarded-For/-Proto
|
|
# from these addresses (real client IP in audit/session records, scheme=https).
|
|
FORWARDED_ALLOW_IPS: ${FORWARDED_ALLOW_IPS:-127.0.0.1}
|
|
# 1 = session/CSRF cookies are sent only over HTTPS. Set once TLS is in front.
|
|
WEB_COOKIE_SECURE: ${WEB_COOKIE_SECURE:-0}
|
|
# AGENT_DEV_TOKEN is intentionally NOT set: real Agents are registered with central/scripts/register_agent.py.
|
|
ports:
|
|
- "${HUB_BIND:-127.0.0.1}:${HUB_PORT:-8000}:8000"
|
|
networks: [internal, edge]
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 10
|
|
start_period: 30s
|
|
logging: *logging
|
|
|
|
networks:
|
|
internal:
|
|
internal: true # no route to the outside: PostgreSQL is never exposed
|
|
edge: {}
|
|
|
|
volumes:
|
|
postgres_data:
|