86 lines
4.0 KiB
YAML
86 lines
4.0 KiB
YAML
# 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:
|