Files
ModelForge/docker-compose.yml

155 lines
6.7 KiB
YAML

services:
postgres:
image: postgres:17-alpine@sha256:18cfe3ef5e6815560c98237d6216d1e5119702fb0f3894c8785dd58b8bbe5d73
environment:
POSTGRES_DB: ${MODELFORGE_POSTGRES_DB:-modelforge}
POSTGRES_USER: ${MODELFORGE_POSTGRES_ADMIN_USER:-postgres}
POSTGRES_PASSWORD: ${MODELFORGE_POSTGRES_ADMIN_PASSWORD:?set a bootstrap-only PostgreSQL admin password}
MODELFORGE_MIGRATION_DB_PASSWORD: ${MODELFORGE_MIGRATION_DB_PASSWORD:?set the non-superuser migration role password}
MODELFORGE_RUNTIME_DB_PASSWORD: ${MODELFORGE_RUNTIME_DB_PASSWORD:?set the non-owner runtime role password}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./deploy/postgres/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${MODELFORGE_POSTGRES_ADMIN_USER:-postgres} -d ${MODELFORGE_POSTGRES_DB:-modelforge}"]
interval: 5s
timeout: 3s
retries: 20
ports:
# Loopback only. The control-plane database holds provenance, credential hashes and the
# audit trail; publishing it on every interface put all of that on the LAN behind a
# development password. Override deliberately if a remote tool genuinely needs it.
- "${MODELFORGE_POSTGRES_BIND:-127.0.0.1}:${MODELFORGE_POSTGRES_PORT:-5432}:5432"
redis:
image: redis:7-alpine@sha256:ff02b58f971e7d7d156a1267e283fcbbeee91773b6aa36c49dac28ecfe28eadf
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 20
ports:
# Loopback only. Redis has no authentication configured and holds transient request payloads.
- "${MODELFORGE_REDIS_BIND:-127.0.0.1}:${MODELFORGE_REDIS_PORT:-6379}:6379"
api:
build:
context: ./backend
args:
# Build identity, so any image built from this file can say where it came from. An argument
# that is never passed stays empty and is reported as null rather than guessed.
MODELFORGE_VERSION: ${MODELFORGE_VERSION:-0.0.0}
MODELFORGE_COMMIT: ${MODELFORGE_COMMIT:-}
MODELFORGE_BUILT_AT: ${MODELFORGE_BUILT_AT:-}
environment:
MODELFORGE_DATABASE_URL: ${MODELFORGE_RUNTIME_DATABASE_URL:?set the modelforge_runtime SQLAlchemy URL}
MODELFORGE_REDIS_URL: redis://redis:6379/0
MODELFORGE_HF_HOME: /data/hf-cache
MODELFORGE_ARTIFACT_ROOT: /data/artifacts
MODELFORGE_QUARANTINE_ROOT: /data/quarantine
MODELFORGE_CONFIG_ROOT: /app/config
MODELFORGE_NODE_IDENTITY_FILE: /data/state/node-id
MODELFORGE_NODE_IDENTITY_MODE: persisted
MODELFORGE_HARDWARE_REFRESH_ON_STARTUP: "false"
MODELFORGE_NODE_LIVENESS_MONITOR_ENABLED: "true"
MODELFORGE_REGISTRY_SEED_ON_STARTUP: "true"
MODELFORGE_OPERATOR_API_KEY: ${MODELFORGE_OPERATOR_API_KEY:-}
MODELFORGE_HF_TOKEN: ${MODELFORGE_HF_TOKEN:-}
MODELFORGE_NODE_STALE_AFTER_SECONDS: ${MODELFORGE_NODE_STALE_AFTER_SECONDS:-30}
MODELFORGE_NODE_OFFLINE_AFTER_SECONDS: ${MODELFORGE_NODE_OFFLINE_AFTER_SECONDS:-90}
MODELFORGE_LIVENESS_POLL_INTERVAL_SECONDS: ${MODELFORGE_LIVENESS_POLL_INTERVAL_SECONDS:-5}
MODELFORGE_SERVING_RECONCILIATION_ENABLED: ${MODELFORGE_SERVING_RECONCILIATION_ENABLED:-true}
MODELFORGE_LIFECYCLE_RECONCILIATION_ENABLED: ${MODELFORGE_LIFECYCLE_RECONCILIATION_ENABLED:-true}
MODELFORGE_MIGRATION_RECONCILIATION_ENABLED: ${MODELFORGE_MIGRATION_RECONCILIATION_ENABLED:-true}
MODELFORGE_OBSERVABILITY_MONITOR_ENABLED: ${MODELFORGE_OBSERVABILITY_MONITOR_ENABLED:-true}
MODELFORGE_RECOVERY_RECONCILIATION_ENABLED: ${MODELFORGE_RECOVERY_RECONCILIATION_ENABLED:-true}
MODELFORGE_SOURCE_COMMIT: ${MODELFORGE_SOURCE_COMMIT:-}
MODELFORGE_SOURCE_REFERENCE: ${MODELFORGE_SOURCE_REFERENCE:-}
MODELFORGE_SOURCE_REPOSITORY: ${MODELFORGE_SOURCE_REPOSITORY:-}
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_BACKUP_STALE_AFTER_SECONDS: ${MODELFORGE_BACKUP_STALE_AFTER_SECONDS:-93600}
MODELFORGE_RESTORE_ALLOW_PRODUCTION_TARGET: ${MODELFORGE_RESTORE_ALLOW_PRODUCTION_TARGET:-false}
MODELFORGE_OBSERVABILITY_POLL_INTERVAL_SECONDS: ${MODELFORGE_OBSERVABILITY_POLL_INTERVAL_SECONDS:-60}
MODELFORGE_SERVING_RECONCILIATION_INTERVAL_SECONDS: ${MODELFORGE_SERVING_RECONCILIATION_INTERVAL_SECONDS:-5}
MODELFORGE_SCHEDULER_SAFETY_RESERVE_BYTES: ${MODELFORGE_SCHEDULER_SAFETY_RESERVE_BYTES:-1073741824}
volumes:
- ./config:/app/config:ro
- hf-cache:/data/hf-cache
- model-artifacts:/data/artifacts
- quarantine:/data/quarantine
- modelforge-state:/data/state
- ${MODELFORGE_BACKUP_VOLUME:-modelforge-backups}:/data/backups
- ${MODELFORGE_RESTORE_VOLUME:-modelforge-restore}:/data/restore
ports:
- "${MODELFORGE_API_BIND:-0.0.0.0}:${MODELFORGE_API_PUBLISHED_PORT:-8000}:8000"
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
depends_on:
migrate:
condition: service_completed_successfully
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/v1/health/live')"]
interval: 10s
timeout: 3s
retries: 10
migrate:
build:
context: ./backend
args:
MODELFORGE_VERSION: ${MODELFORGE_VERSION:-0.0.0}
MODELFORGE_COMMIT: ${MODELFORGE_COMMIT:-}
MODELFORGE_BUILT_AT: ${MODELFORGE_BUILT_AT:-}
command: ["alembic", "upgrade", "head"]
environment:
MODELFORGE_MIGRATION_DATABASE_URL: ${MODELFORGE_MIGRATION_DATABASE_URL:?set the non-superuser modelforge owner SQLAlchemy URL}
depends_on:
postgres:
condition: service_healthy
restart: "no"
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
web:
build:
context: ./frontend
args:
VITE_API_BASE_URL: ${VITE_API_BASE_URL:-http://localhost:8000}
MODELFORGE_VERSION: ${MODELFORGE_VERSION:-0.0.0}
MODELFORGE_COMMIT: ${MODELFORGE_COMMIT:-}
MODELFORGE_BUILT_AT: ${MODELFORGE_BUILT_AT:-}
ports:
- "${MODELFORGE_WEB_BIND:-127.0.0.1}:${MODELFORGE_WEB_PORT:-3000}:3000"
read_only: true
tmpfs:
- /tmp:size=16m,mode=1777
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
depends_on:
api:
condition: service_healthy
volumes:
postgres-data:
modelforge-backups:
modelforge-restore:
redis-data:
hf-cache:
model-artifacts:
quarantine:
modelforge-state: