# Docker Compose for Oikos development # Usage: docker compose up -d postgres (just the DB) # make dev (full dev stack) # # The SPA isn't embedded in the oikos binary (see # plans/2026-07-12-wails-desktop-app.md 0.1/0.6) but it IS part of this # stack as its own `web` service (compose/web/Dockerfile), so it deploys # through the same push-to-main pipeline as everything else. `npm run dev` # in web/ is still the fast local-iteration path. services: postgres: image: timescale/timescaledb:2.17.2-pg16 restart: unless-stopped environment: POSTGRES_DB: oikos POSTGRES_USER: oikos POSTGRES_PASSWORD: ${OIKOS_DB_PASSWORD:-oikos_dev} ports: - "5432:5432" volumes: - pg-data:/var/lib/postgresql/data healthcheck: test: ["CMD", "pg_isready", "-U", "oikos"] interval: 5s timeout: 5s retries: 5 # One-shot: run migrations then exit migrate: build: context: . dockerfile: compose/oikos/Dockerfile depends_on: postgres: condition: service_healthy environment: OIKOS_DATABASE_URL: postgres://oikos:${OIKOS_DB_PASSWORD:-oikos_dev}@postgres:5432/oikos?sslmode=disable command: ["migrate"] restart: "no" # One-shot: ingest seeds then exit seed: build: context: . dockerfile: compose/oikos/Dockerfile depends_on: migrate: condition: service_completed_successfully environment: OIKOS_DATABASE_URL: postgres://oikos:${OIKOS_DB_PASSWORD:-oikos_dev}@postgres:5432/oikos?sslmode=disable OIKOS_SEEDS_DIR: /seeds command: ["seed"] restart: "no" # API server (Phase 2) api: build: context: . dockerfile: compose/oikos/Dockerfile restart: unless-stopped profiles: ["dev", "full"] depends_on: seed: condition: service_completed_successfully environment: OIKOS_DATABASE_URL: postgres://oikos:${OIKOS_DB_PASSWORD:-oikos_dev}@postgres:5432/oikos?sslmode=disable OIKOS_API_LISTEN: ":8090" OIKOS_ENV: dev OIKOS_DEBUG: "true" # No dev-open auth bypass (plans/2026-07-12-wails-desktop-app.md 0.4) — # every request needs this token. nomos uses the same value to call # back into api's /mcp and /api/v1/approvals/*/decision. OIKOS_MCP_BEARER_TOKEN: ${OIKOS_MCP_BEARER_TOKEN:-dev-token} OIKOS_OIDC_ISSUER: ${OIKOS_OIDC_ISSUER:-https://auth.hubris.network/application/o/oikos/} OIKOS_OIDC_CLIENT_ID: ${OIKOS_OIDC_CLIENT_ID:-otkHBSueHJsYtOHstL6rn5izeGgyOsavp1qA1hod} OIKOS_NOMOS_AGENT_SLUG: ${OIKOS_NOMOS_AGENT_SLUG:-agent:nomos} NOMOS_PROXY_URL: http://nomos:8092 volumes: - ${OIKOS_SSH_KEY_PATH:-~/.ssh/id_ed25519}:/etc/oikos/ssh_key:ro ports: - "8090:8090" command: ["api"] stop_signal: SIGTERM stop_grace_period: 30s # Scheduler (Phase 3) — observe loop scheduler: build: context: . dockerfile: compose/oikos/Dockerfile restart: unless-stopped profiles: ["dev", "full"] depends_on: seed: condition: service_completed_successfully environment: OIKOS_DATABASE_URL: postgres://oikos:${OIKOS_DB_PASSWORD:-oikos_dev}@postgres:5432/oikos?sslmode=disable OIKOS_DEBUG: "true" OIKOS_SCHEDULER_INTERVAL: "30s" OIKOS_SSH_KEY_PATH: /etc/oikos/ssh_key OIKOS_SSH_USER: root volumes: - ${OIKOS_SSH_KEY_PATH:-~/.ssh/id_ed25519}:/etc/oikos/ssh_key:ro cap_add: - NET_RAW command: ["scheduler"] stop_signal: SIGTERM stop_grace_period: 30s # Notifier (Phase 3) — Matrix alerts notifier: build: context: . dockerfile: compose/oikos/Dockerfile restart: unless-stopped profiles: ["dev", "full"] depends_on: seed: condition: service_completed_successfully environment: OIKOS_DATABASE_URL: postgres://oikos:${OIKOS_DB_PASSWORD:-oikos_dev}@postgres:5432/oikos?sslmode=disable OIKOS_DEBUG: "true" OIKOS_APPROVAL_HMAC_SECRET: ${OIKOS_APPROVAL_HMAC_SECRET:-dev-secret} OIKOS_MATRIX_HOMESERVER: ${OIKOS_MATRIX_HOMESERVER:-https://matrix.hubris.network} OIKOS_MATRIX_USER: ${OIKOS_MATRIX_USER:-@hermes:hubris.network} OIKOS_MATRIX_TOKEN: ${OIKOS_MATRIX_TOKEN} OIKOS_MATRIX_ROOM: ${OIKOS_MATRIX_ROOM:-!alerts:hubris.network} command: ["notifier"] stop_signal: SIGTERM stop_grace_period: 30s # Nomos agent gateway (Phase 4) — mesh-published :8092 nomos: build: context: . dockerfile: compose/nomos/Dockerfile restart: unless-stopped profiles: ["full"] depends_on: api: condition: service_started environment: NOMOS_MCP_URL: http://api:8090/mcp NOMOS_AGENT_SLUG: agent:nomos OPENROUTER_API_KEY: ${OPENROUTER_API_KEY} NOMOS_MODEL: ${NOMOS_MODEL:-deepseek/deepseek-v4-pro} DATABASE_URL: postgres://oikos:${OIKOS_DB_PASSWORD:-oikos_dev}@postgres:5432/oikos?sslmode=disable # Must match api's OIKOS_MCP_BEARER_TOKEN above — api's combinedAuth # rejects every request without it now (no dev-open bypass). OIKOS_MCP_BEARER_TOKEN: ${OIKOS_MCP_BEARER_TOKEN:-dev-token} ports: - "8092:8092" stop_signal: SIGTERM stop_grace_period: 10s # Control-room SPA — static build served behind Caddy. The outer # production Caddy (caddy-conf repo, LXC 121) splits /api/*, /mcp, # /agent/* off to api:8090 and sends everything else here; this # container only serves static files with SPA-fallback routing. web: build: context: . dockerfile: compose/web/Dockerfile restart: unless-stopped profiles: ["dev", "full"] ports: - "8091:80" stop_signal: SIGTERM # Redis (required by Infisical — Phase 5) redis: image: redis:7-alpine restart: unless-stopped profiles: ["infisical", "full"] volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 5 # Infisical self-hosted (Phase 5 secrets management) infisical: image: infisical/infisical:latest restart: unless-stopped profiles: ["infisical", "full"] depends_on: postgres: condition: service_healthy redis: condition: service_healthy environment: ENCRYPTION_KEY: ${INFISICAL_ENCRYPTION_KEY:-dev-encryption-key-change-me} JWT_SIGNUP_SECRET: ${INFISICAL_JWT_SIGNUP_SECRET:-dev-jwt-change-me} JWT_REFRESH_SECRET: ${INFISICAL_JWT_REFRESH_SECRET:-dev-refresh-change-me} JWT_AUTH_SECRET: ${INFISICAL_JWT_AUTH_SECRET:-dev-auth-change-me} JWT_SERVICE_SECRET: ${INFISICAL_JWT_SERVICE_SECRET:-dev-service-change-me} JWT_MFA_SECRET: ${INFISICAL_JWT_MFA_SECRET:-dev-mfa-change-me} JWT_PROVIDER_SECRET: ${INFISICAL_JWT_PROVIDER_SECRET:-dev-provider-change-me} SITE_URL: ${INFISICAL_SITE_URL:-http://localhost:8080} SMTP_HOST: "" DB_CONNECTION_URI: postgres://oikos:${OIKOS_DB_PASSWORD:-oikos_dev}@postgres:5432/infisical?sslmode=disable REDIS_URL: redis://redis:6379 ports: - "8080:8080" volumes: pg-data: redis-data: