# Isolated disaster-recovery rehearsal environment. # # A DR rehearsal that reuses the live database volume proves nothing. This projection provisions a # genuinely separate PostgreSQL 17 server and a second ModelForge control plane bound to it, so a # verified backup can be restored, reconciled and validated without touching production state. # # docker compose -p modelforge --env-file .env \ # -f docker-compose.yml -f docker-compose.dr.yml up -d dr-postgres dr-api # # The DR services publish on separate ports and use their own volumes. Nothing here writes to the # production database, to ExampleRAG, or to ExampleVision. services: dr-postgres: image: postgres:17-alpine@sha256:18cfe3ef5e6815560c98237d6216d1e5119702fb0f3894c8785dd58b8bbe5d73 environment: POSTGRES_DB: ${MODELFORGE_DR_DB:-mf_restore} POSTGRES_USER: ${MODELFORGE_DR_USER:-modelforge} POSTGRES_PASSWORD: ${MODELFORGE_DR_PASSWORD:-modelforge} volumes: - dr-postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${MODELFORGE_DR_USER:-modelforge} -d postgres"] interval: 5s timeout: 3s retries: 20 ports: # Loopback only. A DR rehearsal restores the whole control-plane database here, so during a # rehearsal this holds provenance, credential hashes and the audit trail. Published on every # interface it accepted the development credentials from the LAN as a superuser. - "${MODELFORGE_DR_POSTGRES_BIND:-127.0.0.1}:${MODELFORGE_DR_POSTGRES_PORT:-5433}:5432" dr-api: build: context: ./backend env_file: - path: .env required: false environment: # The rebuilt control plane runs against the restored database only. MODELFORGE_DATABASE_URL: postgresql+psycopg://${MODELFORGE_DR_USER:-modelforge}:${MODELFORGE_DR_PASSWORD:-modelforge}@dr-postgres:5432/${MODELFORGE_DR_DB:-mf_restore} MODELFORGE_REDIS_URL: redis://redis:6379/1 MODELFORGE_CONFIG_ROOT: /app/config MODELFORGE_ENV: development MODELFORGE_OPERATOR_API_KEY: ${MODELFORGE_OPERATOR_API_KEY:-} MODELFORGE_BACKUP_ROOT: /data/backups MODELFORGE_BACKUP_RESTORE_ROOT: /data/restore MODELFORGE_BACKUP_ENCRYPTION_KEY: ${MODELFORGE_BACKUP_ENCRYPTION_KEY:-} MODELFORGE_BACKUP_ENCRYPTION_KEY_ID: ${MODELFORGE_BACKUP_ENCRYPTION_KEY_ID:-modelforge-backup-key-1} MODELFORGE_SOURCE_COMMIT: ${MODELFORGE_SOURCE_COMMIT:-} MODELFORGE_SOURCE_REFERENCE: ${MODELFORGE_SOURCE_REFERENCE:-} MODELFORGE_SOURCE_REPOSITORY: ${MODELFORGE_SOURCE_REPOSITORY:-} # A recovered control plane re-measures current truth; it never resurrects a snapshot. MODELFORGE_HARDWARE_REFRESH_ON_STARTUP: "false" MODELFORGE_NODE_LIVENESS_MONITOR_ENABLED: "true" MODELFORGE_REGISTRY_SEED_ON_STARTUP: "false" MODELFORGE_SERVING_RECONCILIATION_ENABLED: "false" MODELFORGE_LIFECYCLE_RECONCILIATION_ENABLED: "true" MODELFORGE_MIGRATION_RECONCILIATION_ENABLED: "true" MODELFORGE_OBSERVABILITY_MONITOR_ENABLED: "true" MODELFORGE_RECOVERY_RECONCILIATION_ENABLED: "true" volumes: - ./config:/app/config:ro - ${MODELFORGE_BACKUP_VOLUME:-modelforge-backups}:/data/backups:ro - dr-restore:/data/restore ports: # Loopback only. This is a rehearsal control plane serving restored data; it has no reason to # be reachable from the network the production API is published on. - "${MODELFORGE_DR_API_BIND:-127.0.0.1}:${MODELFORGE_DR_API_PORT:-8001}:8000" depends_on: dr-postgres: condition: service_healthy # The rebuilt control plane must never run migrations against a database it has not restored. command: ["sh", "-c", "uvicorn modelforge_api.main:app --host 0.0.0.0 --port 8000"] healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/v1/health/live')"] interval: 10s timeout: 3s retries: 10 volumes: dr-postgres-data: dr-restore: