Adds cowork-service (port 4009) to docker-compose.yml with healthcheck,
depends_on gates for cosmos-emulator and platform-service, env_file
integration, and Traefik labels. Unblocks Phase 3 ecosystem wiring of
the ByteLyst roadmap.
Also adds the services/cowork-service/Dockerfile that compose builds
from. Pattern mirrors services/mcp-server/Dockerfile but copies the
full workspace in one step rather than enumerating every package.json,
to stay resilient to workspace membership changes. Production stage
runs `node dist/server.js` on :4009 with BusyBox-wget healthcheck
(bundled with node:22-alpine — no apk install required).
.env.example gains a Cowork-Service section documenting:
- ANTHROPIC_API_KEY, RUST_RUNTIME_BIN, RUST_RUNTIME_TIMEOUT_MS
- OLLAMA_URL, OLLAMA_MODELS
- FEATURE_FLAGS_ENABLED
The 13th clawcowork flag telemetry_enabled already ships via COMMON_FLAGS
in services/platform-service/src/modules/flags/seed.ts so seed.ts was not
touched.
Gap: INFRA-gap-01
Verified: docker compose config (YAML validity + env substitution),
pnpm -r typecheck / lint / build / test (all green),
docker compose build cowork-service (image built),
docker compose up -d cowork-service --no-deps --wait (Healthy),
curl -fsS localhost:4009/health → {"status":"ok","service":"cowork-service",...}.
Note: full-stack `docker compose up cosmos-emulator platform-service
cowork-service --wait` is blocked by a pre-existing issue in
services/platform-service/Dockerfile (react-native-platform-sdk prepare
script fails during pnpm install --frozen-lockfile in the image build).
That is outside W1 scope; cowork-service starts clean on its own and
becomes Healthy when platform-service is available out-of-band.
257 lines
8.4 KiB
YAML
257 lines
8.4 KiB
YAML
services:
|
|
# ── Mailpit SMTP Sandbox (prototype only) ────────────────────
|
|
mailpit:
|
|
image: axllent/mailpit:v1.27.5
|
|
ports:
|
|
- '1025:1025'
|
|
- '8025:8025'
|
|
healthcheck:
|
|
test: ['CMD', 'wget', '-q', '--spider', 'http://127.0.0.1:8025']
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 6
|
|
restart: unless-stopped
|
|
|
|
# ── Azurite Blob Storage (prototype only) ─────────────────────
|
|
azurite:
|
|
image: mcr.microsoft.com/azure-storage/azurite:3.35.0
|
|
command: azurite-blob --blobHost 0.0.0.0 --blobPort 10000 --location /data --skipApiVersionCheck
|
|
ports:
|
|
- '10000:10000'
|
|
volumes:
|
|
- azurite-data:/data
|
|
healthcheck:
|
|
test:
|
|
[
|
|
'CMD',
|
|
'node',
|
|
'-e',
|
|
'const net=require("net");const s=net.connect(10000,"127.0.0.1",()=>{s.end();process.exit(0)});s.on("error",()=>process.exit(1));',
|
|
]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 6
|
|
restart: unless-stopped
|
|
|
|
# ── Azure Cosmos DB Emulator (prototype only) ─────────────────
|
|
cosmos-emulator:
|
|
image: mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview
|
|
ports:
|
|
- '8081:8081'
|
|
- '1234:1234'
|
|
environment:
|
|
- PROTOCOL=http
|
|
- ENABLE_EXPLORER=true
|
|
- GATEWAY_PUBLIC_ENDPOINT=cosmos-emulator
|
|
healthcheck:
|
|
test:
|
|
[
|
|
'CMD-SHELL',
|
|
'bash -lc ''exec 3<>/dev/tcp/127.0.0.1/8080; printf "GET /ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" >&3; grep -q "200 OK" <&3''',
|
|
]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 12
|
|
start_period: 20s
|
|
restart: unless-stopped
|
|
|
|
# ── Loki (Log Aggregation) ────────────────────────────────────
|
|
loki:
|
|
image: grafana/loki:3.3.2
|
|
ports:
|
|
- '3100:3100'
|
|
volumes:
|
|
- ./services/monitoring/loki/loki-config.yml:/etc/loki/local-config.yaml
|
|
- loki-data:/loki
|
|
command: -config.file=/etc/loki/local-config.yaml
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
# BusyBox wget (used in Alpine images) doesn't support --no-verbose/--tries
|
|
test: ['CMD', 'wget', '-q', '--spider', 'http://127.0.0.1:3100/ready']
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 3
|
|
|
|
# ── Grafana (Log Viewer + Dashboards) ─────────────────────────
|
|
grafana:
|
|
image: grafana/grafana:11.4.0
|
|
ports:
|
|
- '3000:3000'
|
|
environment:
|
|
- GF_SECURITY_ADMIN_USER=admin
|
|
- GF_SECURITY_ADMIN_PASSWORD=lysnrai
|
|
- GF_USERS_ALLOW_SIGN_UP=false
|
|
volumes:
|
|
- ./services/monitoring/grafana/provisioning:/etc/grafana/provisioning
|
|
- ./services/monitoring/grafana/dashboards:/var/lib/grafana/dashboards
|
|
- grafana-data:/var/lib/grafana
|
|
depends_on:
|
|
loki:
|
|
condition: service_started
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ['CMD', 'wget', '-q', '--spider', 'http://127.0.0.1:3000/api/health']
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 3
|
|
|
|
# ── API Gateway (Traefik) ───────────────────────────────────
|
|
gateway:
|
|
image: traefik:v3.3
|
|
command:
|
|
- '--api.insecure=true'
|
|
- '--providers.docker=true'
|
|
- '--providers.docker.exposedbydefault=false'
|
|
- '--entrypoints.web.address=:80'
|
|
- '--accesslog=true'
|
|
- '--accesslog.format=json'
|
|
ports:
|
|
- '80:80'
|
|
- '8080:8080' # Traefik dashboard
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
depends_on:
|
|
loki:
|
|
condition: service_started
|
|
restart: unless-stopped
|
|
|
|
# ── Platform Service (Fastify + TypeScript) ─────────────
|
|
# Consolidated: auth, audit, notifications, flags, blob, invitations, referrals, promos,
|
|
# subscriptions, usage, plans, licenses, stripe, items, comments, votes, public
|
|
platform-service:
|
|
build:
|
|
context: .
|
|
dockerfile: services/platform-service/Dockerfile
|
|
ports:
|
|
- '4003:4003'
|
|
env_file:
|
|
- .env
|
|
environment:
|
|
- PORT=4003
|
|
# Local/dev convenience: ensure Cosmos DB + containers exist.
|
|
- COSMOS_AUTO_INIT=true
|
|
- PLATFORM_SERVICE_URL=http://platform-service:4003
|
|
- EXTRACTION_SERVICE_URL=http://extraction-service:4005
|
|
- MCP_SERVER_URL=http://mcp-server:4007
|
|
- MAILPIT_UI_URL=http://mailpit:8025
|
|
depends_on:
|
|
mailpit:
|
|
condition: service_healthy
|
|
azurite:
|
|
condition: service_healthy
|
|
cosmos-emulator:
|
|
condition: service_healthy
|
|
labels:
|
|
- 'traefik.enable=true'
|
|
- 'traefik.http.routers.platform.rule=PathPrefix(`/api`) || PathPrefix(`/public`) || PathPrefix(`/health`)'
|
|
- 'traefik.http.services.platform.loadbalancer.server.port=4003'
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ['CMD', 'wget', '-q', '--spider', 'http://127.0.0.1:4003/health']
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
# ── Extraction Service (Fastify + TypeScript + Python sidecar) ──
|
|
extraction-service:
|
|
build:
|
|
context: .
|
|
dockerfile: services/extraction-service/Dockerfile
|
|
ports:
|
|
- '4005:4005'
|
|
env_file:
|
|
- .env
|
|
environment:
|
|
- PORT=4005
|
|
- PYTHON_SIDECAR_URL=http://localhost:4006
|
|
depends_on:
|
|
cosmos-emulator:
|
|
condition: service_healthy
|
|
labels:
|
|
- 'traefik.enable=true'
|
|
- 'traefik.http.routers.extraction.rule=PathPrefix(`/api/extract`) || PathPrefix(`/api/tasks`)'
|
|
- 'traefik.http.services.extraction.loadbalancer.server.port=4005'
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test:
|
|
[
|
|
'CMD',
|
|
'node',
|
|
'-e',
|
|
'fetch("http://127.0.0.1:4005/health").then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))',
|
|
]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
# ── MCP Server (Fastify + TypeScript) ────────────────────────
|
|
# Exposes tool namespaces: platform.telemetry.*, platform.diagnostics.*,
|
|
# extraction.*, support.* — consumed by AI agents + admin tooling
|
|
mcp-server:
|
|
build:
|
|
context: .
|
|
dockerfile: services/mcp-server/Dockerfile
|
|
ports:
|
|
- '4007:4007'
|
|
env_file:
|
|
- .env
|
|
environment:
|
|
- PORT=4007
|
|
- PLATFORM_SERVICE_URL=http://platform-service:4003
|
|
- EXTRACTION_SERVICE_URL=http://extraction-service:4005
|
|
depends_on:
|
|
platform-service:
|
|
condition: service_healthy
|
|
extraction-service:
|
|
condition: service_healthy
|
|
labels:
|
|
- 'traefik.enable=true'
|
|
- 'traefik.http.routers.mcp.rule=PathPrefix(`/api/tools`)'
|
|
- 'traefik.http.services.mcp.loadbalancer.server.port=4007'
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ['CMD', 'wget', '-q', '--spider', 'http://127.0.0.1:4007/health']
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
# ── Cowork Service (Fastify bridge to Rust agent runtime) ──────
|
|
# Bridges Tauri desktop / clients and cowork-orchestrator, delegating auth,
|
|
# flags, audit, telemetry, and AI budgets to platform-service.
|
|
cowork-service:
|
|
build:
|
|
context: .
|
|
dockerfile: services/cowork-service/Dockerfile
|
|
ports:
|
|
- '4009:4009'
|
|
env_file:
|
|
- .env
|
|
environment:
|
|
- PORT=4009
|
|
- NODE_ENV=development
|
|
- PRODUCT_ID=clawcowork
|
|
- COSMOS_ENDPOINT=https://cosmos-emulator:8081
|
|
- PLATFORM_SERVICE_URL=http://platform-service:4003
|
|
- EXTRACTION_SERVICE_URL=http://extraction-service:4005
|
|
depends_on:
|
|
cosmos-emulator:
|
|
condition: service_healthy
|
|
platform-service:
|
|
condition: service_healthy
|
|
labels:
|
|
- 'traefik.enable=true'
|
|
- 'traefik.http.routers.cowork.rule=PathPrefix(`/api/cowork`)'
|
|
- 'traefik.http.services.cowork.loadbalancer.server.port=4009'
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ['CMD', 'wget', '-q', '--spider', 'http://127.0.0.1:4009/health']
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 10
|
|
|
|
# ── Volumes ───────────────────────────────────────────────────────
|
|
volumes:
|
|
azurite-data:
|
|
loki-data:
|
|
grafana-data:
|