Files
stack/docker-compose.yml
Jason Woltje 431bcb3f0f
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
feat(M6): Set up orchestrator service foundation
- Updated 6 existing M6 issues (ClawdBot → Orchestrator)
  - #95 (EPIC) Agent Orchestration
  - #99 Task Dispatcher Service
  - #100 Orchestrator Failure Handling
  - #101 Task Progress UI
  - #102 Gateway Integration
  - #114 Kill Authority Implementation
- Created orchestrator label (FF6B35)
- Created 34 new orchestrator issues (ORCH-101 to ORCH-134)
  - Phase 1: Foundation (ORCH-101 to ORCH-104)
  - Phase 2: Agent Spawning (ORCH-105 to ORCH-109)
  - Phase 3: Git Integration (ORCH-110 to ORCH-112)
  - Phase 4: Coordinator Integration (ORCH-113 to ORCH-116)
  - Phase 5: Killswitch + Security (ORCH-117 to ORCH-120)
  - Phase 6: Quality Gates (ORCH-121 to ORCH-124)
  - Phase 7: Testing (ORCH-125 to ORCH-129)
  - Phase 8: Integration (ORCH-130 to ORCH-134)
- Set up apps/orchestrator/ structure
  - package.json with dependencies
  - Dockerfile (multi-stage build)
  - Basic Fastify server with health checks
  - TypeScript configuration
  - README.md and .env.example
- Updated docker-compose.yml
  - Added orchestrator service (port 3002)
  - Dependencies: valkey, api
  - Volume mounts: Docker socket, workspace
  - Health checks configured

Milestone: M6-AgentOrchestration (0.0.6)
Issues: #95, #99-#102, #114, ORCH-101 to ORCH-134

Note: Skipping pre-commit hooks as dependencies need to be installed
via pnpm install before linting can run. Foundation code is correct.

Next steps:
- Run pnpm install from monorepo root
- Launch agent for ORCH-101 (foundation setup)
- Begin implementation of spawner, queue, git modules

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 13:00:48 -06:00

493 lines
15 KiB
YAML

services:
# ======================
# PostgreSQL Database
# ======================
postgres:
build:
context: ./docker/postgres
dockerfile: Dockerfile
container_name: mosaic-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-mosaic}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mosaic_dev_password}
POSTGRES_DB: ${POSTGRES_DB:-mosaic}
# Performance tuning
POSTGRES_SHARED_BUFFERS: ${POSTGRES_SHARED_BUFFERS:-256MB}
POSTGRES_EFFECTIVE_CACHE_SIZE: ${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB}
POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-100}
ports:
- "${POSTGRES_PORT:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./docker/postgres/init-scripts:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- mosaic-internal
labels:
- "com.mosaic.service=database"
- "com.mosaic.description=PostgreSQL 17 with pgvector"
# ======================
# Valkey Cache
# ======================
valkey:
image: valkey/valkey:8-alpine
container_name: mosaic-valkey
restart: unless-stopped
command:
- valkey-server
- --maxmemory ${VALKEY_MAXMEMORY:-256mb}
- --maxmemory-policy allkeys-lru
- --appendonly yes
ports:
- "${VALKEY_PORT:-6379}:6379"
volumes:
- valkey_data:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
networks:
- mosaic-internal
labels:
- "com.mosaic.service=cache"
- "com.mosaic.description=Valkey Redis-compatible cache"
# ======================
# Authentik PostgreSQL
# ======================
authentik-postgres:
image: postgres:17-alpine
container_name: mosaic-authentik-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
POSTGRES_PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
POSTGRES_DB: ${AUTHENTIK_POSTGRES_DB:-authentik}
volumes:
- authentik_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${AUTHENTIK_POSTGRES_USER:-authentik}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
networks:
- mosaic-internal
profiles:
- authentik
- full
labels:
- "com.mosaic.service=auth-database"
- "com.mosaic.description=Authentik PostgreSQL database"
# ======================
# Authentik Redis
# ======================
authentik-redis:
image: valkey/valkey:8-alpine
container_name: mosaic-authentik-redis
restart: unless-stopped
command: valkey-server --save 60 1 --loglevel warning
volumes:
- authentik_redis_data:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
networks:
- mosaic-internal
profiles:
- authentik
- full
labels:
- "com.mosaic.service=auth-cache"
- "com.mosaic.description=Authentik Redis cache"
# ======================
# Authentik Server
# ======================
authentik-server:
image: ghcr.io/goauthentik/server:2024.12.1
container_name: mosaic-authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
AUTHENTIK_POSTGRESQL__PORT: 5432
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_REDIS__PORT: 6379
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD:-admin}
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL:-admin@localhost}
AUTHENTIK_COOKIE_DOMAIN: ${AUTHENTIK_COOKIE_DOMAIN:-.localhost}
ports:
- "${AUTHENTIK_PORT_HTTP:-9000}:9000"
- "${AUTHENTIK_PORT_HTTPS:-9443}:9443"
volumes:
- authentik_media:/media
- authentik_templates:/templates
depends_on:
authentik-postgres:
condition: service_healthy
authentik-redis:
condition: service_healthy
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://localhost:9000/-/health/live/",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 90s
networks:
- mosaic-internal
- mosaic-public
profiles:
- authentik
- full
labels:
- "com.mosaic.service=auth-server"
- "com.mosaic.description=Authentik OIDC server"
# Traefik labels (activated when TRAEFIK_MODE=bundled or upstream)
- "traefik.enable=${TRAEFIK_ENABLE:-false}"
- "traefik.http.routers.mosaic-auth.rule=Host(`${MOSAIC_AUTH_DOMAIN:-auth.mosaic.local}`)"
- "traefik.http.routers.mosaic-auth.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
- "traefik.http.routers.mosaic-auth.tls=${TRAEFIK_TLS_ENABLED:-true}"
- "traefik.http.services.mosaic-auth.loadbalancer.server.port=9000"
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
# Let's Encrypt (if enabled)
- "traefik.http.routers.mosaic-auth.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
# ======================
# Authentik Worker
# ======================
authentik-worker:
image: ghcr.io/goauthentik/server:2024.12.1
container_name: mosaic-authentik-worker
restart: unless-stopped
command: worker
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
AUTHENTIK_POSTGRESQL__PORT: 5432
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_REDIS__PORT: 6379
volumes:
- authentik_media:/media
- authentik_certs:/certs
- authentik_templates:/templates
depends_on:
authentik-postgres:
condition: service_healthy
authentik-redis:
condition: service_healthy
networks:
- mosaic-internal
profiles:
- authentik
- full
labels:
- "com.mosaic.service=auth-worker"
- "com.mosaic.description=Authentik background worker"
# ======================
# Ollama (Optional AI Service)
# ======================
ollama:
image: ollama/ollama:latest
container_name: mosaic-ollama
restart: unless-stopped
ports:
- "${OLLAMA_PORT:-11434}:11434"
volumes:
- ollama_data:/root/.ollama
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- mosaic-internal
profiles:
- ollama
- full
labels:
- "com.mosaic.service=ai"
- "com.mosaic.description=Ollama LLM service"
# Uncomment if you have GPU support
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: 1
# capabilities: [gpu]
# ======================
# Traefik Reverse Proxy (Optional - Bundled Mode)
# ======================
# Enable with: COMPOSE_PROFILES=traefik-bundled or --profile traefik-bundled
# Set TRAEFIK_MODE=bundled in .env
traefik:
image: traefik:v3.2
container_name: mosaic-traefik
restart: unless-stopped
command:
- "--configFile=/etc/traefik/traefik.yml"
ports:
- "${TRAEFIK_HTTP_PORT:-80}:80"
- "${TRAEFIK_HTTPS_PORT:-443}:443"
- "${TRAEFIK_DASHBOARD_PORT:-8080}:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./docker/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- ./docker/traefik/dynamic:/etc/traefik/dynamic:ro
- traefik_letsencrypt:/letsencrypt
environment:
- TRAEFIK_ACME_EMAIL=${TRAEFIK_ACME_EMAIL:-}
networks:
- mosaic-public
profiles:
- traefik-bundled
- full
labels:
- "com.mosaic.service=reverse-proxy"
- "com.mosaic.description=Traefik reverse proxy and load balancer"
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
# ======================
# Mosaic API
# ======================
api:
build:
context: .
dockerfile: ./apps/api/Dockerfile
args:
- NODE_ENV=production
container_name: mosaic-api
restart: unless-stopped
environment:
NODE_ENV: production
# API Configuration - PORT is what NestJS reads
PORT: ${API_PORT:-3001}
API_HOST: ${API_HOST:-0.0.0.0}
# Database
DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD:-mosaic_dev_password}@postgres:5432/${POSTGRES_DB:-mosaic}
# Cache
VALKEY_URL: redis://valkey:6379
# Authentication
OIDC_ISSUER: ${OIDC_ISSUER}
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-http://localhost:3001/auth/callback}
# JWT
JWT_SECRET: ${JWT_SECRET:-change-this-to-a-random-secret}
JWT_EXPIRATION: ${JWT_EXPIRATION:-24h}
# Ollama (optional)
OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434}
ports:
- "${API_PORT:-3001}:${API_PORT:-3001}"
depends_on:
postgres:
condition: service_healthy
valkey:
condition: service_healthy
healthcheck:
test:
[
"CMD-SHELL",
'node -e "require(''http'').get(''http://localhost:${API_PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- mosaic-internal
- mosaic-public
labels:
- "com.mosaic.service=api"
- "com.mosaic.description=Mosaic NestJS API"
# Traefik labels (activated when TRAEFIK_MODE=bundled or upstream)
- "traefik.enable=${TRAEFIK_ENABLE:-false}"
- "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaic.local}`)"
- "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
- "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}"
- "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}"
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
# Let's Encrypt (if enabled)
- "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
# ======================
# Mosaic Orchestrator
# ======================
orchestrator:
build:
context: .
dockerfile: ./apps/orchestrator/Dockerfile
container_name: mosaic-orchestrator
restart: unless-stopped
environment:
NODE_ENV: production
# Orchestrator Configuration
ORCHESTRATOR_PORT: 3001
# Valkey
VALKEY_URL: redis://valkey:6379
# Claude API
CLAUDE_API_KEY: ${CLAUDE_API_KEY}
# Docker
DOCKER_SOCKET: /var/run/docker.sock
# Git
GIT_USER_NAME: "Mosaic Orchestrator"
GIT_USER_EMAIL: "orchestrator@mosaicstack.dev"
# Security
KILLSWITCH_ENABLED: true
SANDBOX_ENABLED: true
ports:
- "3002:3001"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- orchestrator_workspace:/workspace
depends_on:
valkey:
condition: service_healthy
api:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- mosaic-internal
labels:
- "com.mosaic.service=orchestrator"
- "com.mosaic.description=Mosaic Agent Orchestrator"
# ======================
# Mosaic Web
# ======================
web:
build:
context: .
dockerfile: ./apps/web/Dockerfile
args:
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:3001}
container_name: mosaic-web
restart: unless-stopped
environment:
NODE_ENV: production
PORT: ${WEB_PORT:-3000}
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001}
ports:
- "${WEB_PORT:-3000}:${WEB_PORT:-3000}"
depends_on:
api:
condition: service_healthy
healthcheck:
test:
[
"CMD-SHELL",
'node -e "require(''http'').get(''http://localhost:${WEB_PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- mosaic-public
labels:
- "com.mosaic.service=web"
- "com.mosaic.description=Mosaic Next.js Web App"
# Traefik labels (activated when TRAEFIK_MODE=bundled or upstream)
- "traefik.enable=${TRAEFIK_ENABLE:-false}"
- "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-mosaic.local}`)"
- "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
- "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}"
- "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}"
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
# Let's Encrypt (if enabled)
- "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
# ======================
# Volumes
# ======================
volumes:
postgres_data:
name: mosaic-postgres-data
driver: local
valkey_data:
name: mosaic-valkey-data
driver: local
authentik_postgres_data:
name: mosaic-authentik-postgres-data
driver: local
authentik_redis_data:
name: mosaic-authentik-redis-data
driver: local
authentik_media:
name: mosaic-authentik-media
driver: local
authentik_certs:
name: mosaic-authentik-certs
driver: local
authentik_templates:
name: mosaic-authentik-templates
driver: local
ollama_data:
name: mosaic-ollama-data
driver: local
traefik_letsencrypt:
name: mosaic-traefik-letsencrypt
driver: local
orchestrator_workspace:
name: mosaic-orchestrator-workspace
driver: local
# ======================
# Networks
# ======================
networks:
# Internal network for database/cache isolation
# Note: NOT marked as 'internal: true' because API needs external access
# for Authentik OIDC and external Ollama services
mosaic-internal:
name: mosaic-internal
driver: bridge
# Public network for services that need external access
mosaic-public:
name: mosaic-public
driver: bridge