# ============================================== # Mosaic Stack — Coolify Core Deployment # ============================================== # # Core services only. For Matrix, speech, and other optional # services, deploy them as separate Coolify services or extend # this file. # # Usage (Coolify): # 1. New Resource -> Docker Compose # 2. Paste this file # 3. Set environment variables in Coolify UI # 4. Configure domains for web + api in Coolify UI # 5. Deploy # # NOTE: Traefik labels are NOT included here. Coolify manages # routing and TLS via its own proxy integration. Configure # domains in the Coolify service settings. # # ============================================== services: # ====================== # PostgreSQL Database # ====================== postgres: image: git.mosaicstack.dev/mosaic/stack-postgres:${IMAGE_TAG:-latest} restart: unless-stopped environment: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB} - POSTGRES_SHARED_BUFFERS=${POSTGRES_SHARED_BUFFERS:-256MB} - POSTGRES_EFFECTIVE_CACHE_SIZE=${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB} - POSTGRES_MAX_CONNECTIONS=${POSTGRES_MAX_CONNECTIONS:-100} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 start_period: 30s networks: - internal # ====================== # Valkey Cache # ====================== valkey: image: valkey/valkey:8-alpine restart: unless-stopped command: - valkey-server - --maxmemory ${VALKEY_MAXMEMORY:-256mb} - --maxmemory-policy noeviction - --appendonly yes volumes: - valkey_data:/data healthcheck: test: ["CMD", "valkey-cli", "ping"] interval: 10s timeout: 5s retries: 5 start_period: 10s networks: - internal # ====================== # Mosaic API # ====================== api: image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest} restart: unless-stopped environment: # Coolify domain assignment (magic variable — tells Coolify this service gets a domain on port 3001) - SERVICE_FQDN_API_3001 - NODE_ENV=production - PORT=${API_PORT:-3001} - API_HOST=${API_HOST:-0.0.0.0} # Database - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} # Cache - VALKEY_URL=redis://valkey:6379 # Auth (external Authentik — optional) - OIDC_ENABLED=${OIDC_ENABLED:-false} - OIDC_ISSUER=${OIDC_ISSUER:-} - OIDC_CLIENT_ID=${OIDC_CLIENT_ID:-} - OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET:-} - OIDC_REDIRECT_URI=${OIDC_REDIRECT_URI:-} # JWT - JWT_SECRET=${JWT_SECRET} - JWT_EXPIRATION=${JWT_EXPIRATION:-24h} # Better Auth - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET} - BETTER_AUTH_URL=${BETTER_AUTH_URL:-} - CSRF_SECRET=${CSRF_SECRET} - COOKIE_DOMAIN=${COOKIE_DOMAIN:-} # Encryption - ENCRYPTION_KEY=${ENCRYPTION_KEY} # External services (optional — leave empty to disable) - OLLAMA_ENDPOINT=${OLLAMA_ENDPOINT:-} - OLLAMA_MODEL=${OLLAMA_MODEL:-llama3.2} - OPENBAO_ADDR=${OPENBAO_ADDR:-} # Knowledge module - KNOWLEDGE_CACHE_ENABLED=${KNOWLEDGE_CACHE_ENABLED:-true} - KNOWLEDGE_CACHE_TTL=${KNOWLEDGE_CACHE_TTL:-300} - SEMANTIC_SEARCH_SIMILARITY_THRESHOLD=${SEMANTIC_SEARCH_SIMILARITY_THRESHOLD:-0.5} # Rate limiting - RATE_LIMIT_TTL=${RATE_LIMIT_TTL:-60} - RATE_LIMIT_GLOBAL_LIMIT=${RATE_LIMIT_GLOBAL_LIMIT:-100} - RATE_LIMIT_STORAGE=${RATE_LIMIT_STORAGE:-redis} # Speech services (disabled — not in core stack) - STT_ENABLED=${STT_ENABLED:-false} - TTS_ENABLED=${TTS_ENABLED:-false} # Matrix bridge (disabled — not in core stack) - MATRIX_ACCESS_TOKEN=${MATRIX_ACCESS_TOKEN:-} # Telemetry (disabled by default) - MOSAIC_TELEMETRY_ENABLED=${MOSAIC_TELEMETRY_ENABLED:-false} - MOSAIC_TELEMETRY_SERVER_URL=${MOSAIC_TELEMETRY_SERVER_URL:-} - MOSAIC_TELEMETRY_API_KEY=${MOSAIC_TELEMETRY_API_KEY:-} - MOSAIC_TELEMETRY_INSTANCE_ID=${MOSAIC_TELEMETRY_INSTANCE_ID:-} - MOSAIC_TELEMETRY_DRY_RUN=${MOSAIC_TELEMETRY_DRY_RUN:-false} # Frontend URLs (for CORS and auth redirects) - NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} - TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-} 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: - internal # ====================== # Mosaic Web # ====================== web: image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest} restart: unless-stopped environment: # Coolify domain assignment (magic variable — tells Coolify this service gets a domain on port 3000) - SERVICE_FQDN_WEB_3000 - NODE_ENV=production - PORT=${WEB_PORT:-3000} - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} - NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} - NEXT_PUBLIC_ORCHESTRATOR_URL=${NEXT_PUBLIC_ORCHESTRATOR_URL:-} - NEXT_PUBLIC_AUTH_MODE=${NEXT_PUBLIC_AUTH_MODE:-real} - ORCHESTRATOR_API_KEY=${ORCHESTRATOR_API_KEY:-} 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: - internal # ====================== # Mosaic Coordinator # ====================== coordinator: image: git.mosaicstack.dev/mosaic/stack-coordinator:${IMAGE_TAG:-latest} restart: unless-stopped environment: - GITEA_WEBHOOK_SECRET=${GITEA_WEBHOOK_SECRET:-} - GITEA_URL=${GITEA_URL:-} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} - LOG_LEVEL=${LOG_LEVEL:-info} - HOST=0.0.0.0 - PORT=8000 - COORDINATOR_POLL_INTERVAL=${COORDINATOR_POLL_INTERVAL:-5.0} - COORDINATOR_MAX_CONCURRENT_AGENTS=${COORDINATOR_MAX_CONCURRENT_AGENTS:-10} - COORDINATOR_ENABLED=${COORDINATOR_ENABLED:-true} # Telemetry - MOSAIC_TELEMETRY_ENABLED=${MOSAIC_TELEMETRY_ENABLED:-false} - MOSAIC_TELEMETRY_SERVER_URL=${MOSAIC_TELEMETRY_SERVER_URL:-} - MOSAIC_TELEMETRY_API_KEY=${MOSAIC_TELEMETRY_API_KEY:-} - MOSAIC_TELEMETRY_INSTANCE_ID=${MOSAIC_TELEMETRY_INSTANCE_ID:-} - MOSAIC_TELEMETRY_DRY_RUN=${MOSAIC_TELEMETRY_DRY_RUN:-false} healthcheck: test: [ "CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')", ] interval: 30s timeout: 10s retries: 3 start_period: 5s networks: - internal # ====================== # Mosaic Orchestrator # ====================== orchestrator: image: git.mosaicstack.dev/mosaic/stack-orchestrator:${IMAGE_TAG:-latest} restart: unless-stopped user: "1000:1000" environment: - NODE_ENV=production - ORCHESTRATOR_PORT=3001 - AI_PROVIDER=${AI_PROVIDER:-ollama} - OLLAMA_ENDPOINT=${OLLAMA_ENDPOINT:-} - OLLAMA_MODEL=${OLLAMA_MODEL:-llama3.2} - VALKEY_URL=redis://valkey:6379 - VALKEY_HOST=valkey - VALKEY_PORT=6379 - CLAUDE_API_KEY=${CLAUDE_API_KEY:-} - ORCHESTRATOR_API_KEY=${ORCHESTRATOR_API_KEY:-} - DOCKER_SOCKET=/var/run/docker.sock - GIT_USER_NAME=Mosaic Orchestrator - GIT_USER_EMAIL=orchestrator@mosaicstack.dev - KILLSWITCH_ENABLED=true - SANDBOX_ENABLED=true volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - orchestrator_workspace:/workspace depends_on: valkey: condition: service_healthy api: condition: service_healthy healthcheck: test: [ "CMD-SHELL", 'node -e "require(''http'').get(''http://localhost:3001/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"', ] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - internal security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - NET_BIND_SERVICE tmpfs: - /tmp:noexec,nosuid,size=100m # ====================== # Volumes # ====================== volumes: postgres_data: valkey_data: orchestrator_workspace: # ====================== # Networks # ====================== networks: internal: driver: bridge