Files
ModelForge/docker-compose.dr.yml
T

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: