Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
285 lines
9.3 KiB
YAML
285 lines
9.3 KiB
YAML
# ==============================================
|
|
# 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}
|
|
# Server-side orchestrator proxy (API routes forward to orchestrator service over internal network)
|
|
- ORCHESTRATOR_URL=http://orchestrator:3001
|
|
- 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
|
|
# Bind to all interfaces so the web container can reach it over Docker networking
|
|
- HOST=0.0.0.0
|
|
- 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
|