# ============================================================================= # Turnstone Docker Compose Stack — Development # # This file is for local development from a git clone. It builds images # locally from the Dockerfile. If you installed via pip/pipx, run # `turnstone-bootstrap` instead — it writes a production compose.yaml # that pulls pre-built images from ghcr.io. # # Usage: # Infra only: docker compose up # Single node: docker compose --profile production up # Production (PG): TURNSTONE_DB_BACKEND=postgresql docker compose --profile production up # 10-node cluster: docker compose --profile cluster up # ============================================================================= name: turnstone networks: turnstone-net: driver: bridge volumes: turnstone-data: workspace: postgres-data: services: # ------------------------------------------------------------------- # PostgreSQL — production database (profile: production) # ------------------------------------------------------------------- postgres: image: pgautoupgrade/pgautoupgrade:18-alpine profiles: - production - cluster command: - postgres - -c - max_connections=${POSTGRES_MAX_CONNECTIONS:-300} - -c - shared_buffers=128MB environment: POSTGRES_DB: turnstone POSTGRES_USER: ${POSTGRES_USER:-turnstone} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required for production profile} PGDATA: /var/lib/postgresql/data volumes: - postgres-data:/var/lib/postgresql/data networks: - turnstone-net healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-turnstone}"] interval: 5s timeout: 3s retries: 5 start_period: 30s deploy: resources: limits: memory: 4G cpus: '4.0' restart: unless-stopped # ------------------------------------------------------------------- # turnstone-server — Web UI + chat workstreams + LLM interaction # ------------------------------------------------------------------- server: image: turnstone:local profiles: - production command: - sh - -c - >- turnstone-server --host 0.0.0.0 --port 8080 --base-url "$${LLM_BASE_URL}" --api-key "$${OPENAI_API_KEY}" $${MODEL:+--model $$MODEL} $${SKIP_PERMISSIONS:+--skip-permissions} $${MCP_CONFIG:+--mcp-config $$MCP_CONFIG} ports: - "${SERVER_PORT:-8080}:8080" volumes: - turnstone-data:/data - ${WORKSPACE_MOUNT:-workspace}:/workspace environment: - LLM_BASE_URL=${LLM_BASE_URL:-http://host.docker.internal:8000/v1} - OPENAI_API_KEY=${OPENAI_API_KEY:-dummy} - TAVILY_API_KEY=${TAVILY_API_KEY:-} - SKIP_PERMISSIONS=${SKIP_PERMISSIONS:-} # Generate with: python -c "import secrets; print(secrets.token_hex(32))" - TURNSTONE_JWT_SECRET=${TURNSTONE_JWT_SECRET:?Set TURNSTONE_JWT_SECRET in .env} - MODEL=${MODEL:-} - MCP_CONFIG=${MCP_CONFIG:-} - TURNSTONE_DB_BACKEND=${TURNSTONE_DB_BACKEND:-sqlite} - TURNSTONE_DB_URL=${TURNSTONE_DB_URL:-} - TURNSTONE_NODE_ID=${TURNSTONE_NODE_ID:-} - TURNSTONE_ADVERTISE_URL=${TURNSTONE_ADVERTISE_URL:-http://server:8080} extra_hosts: - "host.docker.internal:host-gateway" networks: - turnstone-net depends_on: postgres: condition: service_healthy required: false healthcheck: test: ["CMD", "python", "/usr/local/bin/healthcheck.py", "http://127.0.0.1:8080/health"] interval: 10s timeout: 5s retries: 5 start_period: 60s restart: unless-stopped # ------------------------------------------------------------------- # turnstone-console — Cluster dashboard # ------------------------------------------------------------------- console: image: turnstone:local build: context: . dockerfile: Dockerfile command: - turnstone-console - --host=0.0.0.0 - --port=8090 ports: - "${CONSOLE_PORT:-8090}:8090" environment: # Generate with: python -c "import secrets; print(secrets.token_hex(32))" - TURNSTONE_JWT_SECRET=${TURNSTONE_JWT_SECRET:?Set TURNSTONE_JWT_SECRET in .env} - TURNSTONE_DB_BACKEND=${TURNSTONE_DB_BACKEND:-sqlite} - TURNSTONE_DB_URL=${TURNSTONE_DB_URL:-} - TURNSTONE_CONSOLE_URL=http://console:8090 networks: - turnstone-net healthcheck: test: ["CMD", "python", "/usr/local/bin/healthcheck.py", "http://127.0.0.1:8090/health"] interval: 10s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped # ------------------------------------------------------------------- # turnstone-channel — Channel gateway (Discord, Slack, etc.) # Requires TURNSTONE_DISCORD_TOKEN to enable Discord adapter # ------------------------------------------------------------------- channel: image: turnstone:local profiles: - production - cluster command: - sh - -c - >- turnstone-channel --http-host=0.0.0.0 $${TURNSTONE_DISCORD_GUILD:+--discord-guild $$TURNSTONE_DISCORD_GUILD} environment: - TURNSTONE_DISCORD_TOKEN=${TURNSTONE_DISCORD_TOKEN:-} - TURNSTONE_DISCORD_GUILD=${TURNSTONE_DISCORD_GUILD:-0} # Generate with: python -c "import secrets; print(secrets.token_hex(32))" - TURNSTONE_JWT_SECRET=${TURNSTONE_JWT_SECRET:?Set TURNSTONE_JWT_SECRET in .env} - TURNSTONE_DB_BACKEND=${TURNSTONE_DB_BACKEND:-postgresql} - TURNSTONE_DB_URL=${TURNSTONE_DB_URL:-postgresql+psycopg://${POSTGRES_USER:-turnstone}:${POSTGRES_PASSWORD:-turnstone}@postgres:5432/turnstone} - TURNSTONE_CHANNEL_ADVERTISE_URL=http://channel:8091 networks: - turnstone-net depends_on: postgres: condition: service_healthy required: false restart: unless-stopped # =================================================================== # 10-node cluster (profile: cluster) # # All nodes share the same PostgreSQL instance. # Access via console at :8090. # # Start: docker compose --profile cluster up # =================================================================== # -- cluster servers ------------------------------------------------ server-1: &cluster-server image: turnstone:local build: { context: ., dockerfile: Dockerfile } profiles: [cluster] command: - sh - -c - >- turnstone-server --host 0.0.0.0 --port 8080 --base-url "$${LLM_BASE_URL}" --api-key "$${OPENAI_API_KEY}" $${MODEL:+--model $$MODEL} $${SKIP_PERMISSIONS:+--skip-permissions} $${MCP_CONFIG:+--mcp-config $$MCP_CONFIG} volumes: - turnstone-data:/data - ${WORKSPACE_MOUNT:-workspace}:/workspace environment: &cluster-server-env LLM_BASE_URL: ${LLM_BASE_URL:-http://host.docker.internal:8000/v1} OPENAI_API_KEY: ${OPENAI_API_KEY:-dummy} TAVILY_API_KEY: ${TAVILY_API_KEY:-} SKIP_PERMISSIONS: ${SKIP_PERMISSIONS:-} # Generate with: python -c "import secrets; print(secrets.token_hex(32))" TURNSTONE_JWT_SECRET: ${TURNSTONE_JWT_SECRET:?Set TURNSTONE_JWT_SECRET in .env} MODEL: ${MODEL:-} MCP_CONFIG: ${MCP_CONFIG:-} TURNSTONE_DB_BACKEND: ${TURNSTONE_DB_BACKEND:-postgresql} TURNSTONE_DB_URL: ${TURNSTONE_DB_URL:-postgresql+psycopg://${POSTGRES_USER:-turnstone}:${POSTGRES_PASSWORD:?}@postgres:5432/turnstone} TURNSTONE_NODE_ID: node-1 TURNSTONE_ADVERTISE_URL: http://server-1:8080 extra_hosts: ["host.docker.internal:host-gateway"] networks: [turnstone-net] depends_on: postgres: { condition: service_healthy } healthcheck: test: ["CMD", "python", "/usr/local/bin/healthcheck.py", "http://127.0.0.1:8080/health"] interval: 10s timeout: 5s retries: 5 start_period: 60s deploy: resources: limits: { memory: 4G, cpus: '4' } restart: unless-stopped server-2: <<: *cluster-server environment: { <<: *cluster-server-env, TURNSTONE_NODE_ID: node-2, TURNSTONE_ADVERTISE_URL: "http://server-2:8080" } server-3: <<: *cluster-server environment: { <<: *cluster-server-env, TURNSTONE_NODE_ID: node-3, TURNSTONE_ADVERTISE_URL: "http://server-3:8080" } server-4: <<: *cluster-server environment: { <<: *cluster-server-env, TURNSTONE_NODE_ID: node-4, TURNSTONE_ADVERTISE_URL: "http://server-4:8080" } server-5: <<: *cluster-server environment: { <<: *cluster-server-env, TURNSTONE_NODE_ID: node-5, TURNSTONE_ADVERTISE_URL: "http://server-5:8080" } server-6: <<: *cluster-server environment: { <<: *cluster-server-env, TURNSTONE_NODE_ID: node-6, TURNSTONE_ADVERTISE_URL: "http://server-6:8080" } server-7: <<: *cluster-server environment: { <<: *cluster-server-env, TURNSTONE_NODE_ID: node-7, TURNSTONE_ADVERTISE_URL: "http://server-7:8080" } server-8: <<: *cluster-server environment: { <<: *cluster-server-env, TURNSTONE_NODE_ID: node-8, TURNSTONE_ADVERTISE_URL: "http://server-8:8080" } server-9: <<: *cluster-server environment: { <<: *cluster-server-env, TURNSTONE_NODE_ID: node-9, TURNSTONE_ADVERTISE_URL: "http://server-9:8080" } server-10: <<: *cluster-server environment: { <<: *cluster-server-env, TURNSTONE_NODE_ID: node-10, TURNSTONE_ADVERTISE_URL: "http://server-10:8080" }