- Logs default to warnings and errors (MCP_LOG_LEVEL): the library logged every tool on every request (~48 lines per call). - DNS rebinding protection on /mcp, as the MCP spec asks of HTTP servers: the Host header must be in MCP_ALLOWED_HOSTS (default: localhost; compose default mcp.taskstream.pireon.dev,localhost) and a browser Origin, when present, too; otherwise 403 with a JSON-RPC error. Uses the official validators from @modelcontextprotocol/server. - Test for allowed/foreign Host and Origin; .env.example, compose and docs/09-mcp.md document the variables; Word/PDF regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
135 lines
4.7 KiB
YAML
135 lines
4.7 KiB
YAML
# TaskStream: web + API + infrastructure — production definition (Dokploy deploys this file).
|
|
# No ports are published here: services only talk over the compose network, and the web is
|
|
# exposed through the platform's proxy (Dokploy domain → service `web`, port 80).
|
|
#
|
|
# Services talk to each other by unique aliases (task-stream-*), never by the bare service
|
|
# name: on a shared network (Dokploy's dokploy-network) other projects also have an `api`,
|
|
# `postgres` or `redis`, and Docker DNS would hand out theirs too.
|
|
#
|
|
# Locally, `docker compose` also loads docker-compose.override.yml, which publishes the ports:
|
|
# docker compose up -d --build → everything: web on http://localhost:8080
|
|
# docker compose up -d postgres mongo redis → only infrastructure (then `npx nx serve api` / `npx nx serve web`)
|
|
#
|
|
# Building needs access to Pireon's Nexus (@pi/ui): set NEXUS_TOKEN in the environment or in .env
|
|
# (Dokploy: Environment tab). It reaches the build as a secret and never lands in an image layer.
|
|
services:
|
|
web:
|
|
build:
|
|
context: .
|
|
target: web
|
|
secrets: [nexus_token]
|
|
image: task-stream-web
|
|
expose: ['80']
|
|
networks: [default]
|
|
depends_on:
|
|
api: { condition: service_healthy }
|
|
restart: unless-stopped
|
|
|
|
api:
|
|
build:
|
|
context: .
|
|
target: api
|
|
secrets: [nexus_token]
|
|
image: task-stream-api
|
|
expose: ['3000']
|
|
networks:
|
|
default:
|
|
aliases: [task-stream-api]
|
|
environment:
|
|
POSTGRES_URL: postgres://taskstream:taskstream@task-stream-postgres:5432/taskstream
|
|
# No migrations yet: TypeORM creates the event store and lookup tables.
|
|
POSTGRES_SYNCHRONIZE: 'true'
|
|
MONGO_URL: mongodb://task-stream-mongo:27017/taskstream_read
|
|
REDIS_URL: redis://task-stream-redis:6379
|
|
EVENT_STREAM_KEY: task-stream:events
|
|
EVENTS_BRIDGE_ENABLED: 'true'
|
|
PIREON_BASE_URL: https://oauth.pireon.dev
|
|
PIREON_WEBHOOK_SECRET: ${PIREON_WEBHOOK_SECRET:-}
|
|
PIREON_CLIENT_ID: ${PIREON_CLIENT_ID:-}
|
|
PIREON_CLIENT_SECRET: ${PIREON_CLIENT_SECRET:-}
|
|
# Pireon clients allow one grant type: the one above is authorization_code,
|
|
# this one password (email + password sign-in). Optional.
|
|
PIREON_PASSWORD_CLIENT_ID: ${PIREON_PASSWORD_CLIENT_ID:-}
|
|
PIREON_PASSWORD_CLIENT_SECRET: ${PIREON_PASSWORD_CLIENT_SECRET:-}
|
|
GRAPHQL_PLAYGROUND: 'true'
|
|
# The SDL / OpenAPI files are only useful in development (codegen); the
|
|
# document is still served at /api/docs-json.
|
|
GRAPHQL_SCHEMA_FILE: ''
|
|
OPENAPI_FILE: ''
|
|
depends_on:
|
|
postgres: { condition: service_healthy }
|
|
mongo: { condition: service_healthy }
|
|
redis: { condition: service_healthy }
|
|
restart: unless-stopped
|
|
|
|
# MCP server for AI clients (Claude, Cursor…): Streamable HTTP at /mcp. It consumes the API
|
|
# with each caller's own Pireon token (Authorization header), so it holds no credentials.
|
|
# Optional: it only starts with the `mcp` profile (`docker compose --profile mcp up -d`; in
|
|
# Dokploy, COMPOSE_PROFILES=mcp) and needs a domain → service `mcp`, port 3010.
|
|
mcp:
|
|
profiles: [mcp]
|
|
build:
|
|
context: .
|
|
target: mcp
|
|
secrets: [nexus_token]
|
|
image: task-stream-mcp
|
|
expose: ['3010']
|
|
networks:
|
|
default:
|
|
aliases: [task-stream-mcp]
|
|
environment:
|
|
TASKSTREAM_API_URL: http://task-stream-api:3000
|
|
# Domains it answers on (protection against DNS rebinding): the one Dokploy gives it.
|
|
MCP_ALLOWED_HOSTS: ${MCP_ALLOWED_HOSTS:-mcp.taskstream.pireon.dev,localhost}
|
|
MCP_LOG_LEVEL: ${MCP_LOG_LEVEL:-warn}
|
|
depends_on:
|
|
api: { condition: service_healthy }
|
|
restart: unless-stopped
|
|
|
|
postgres:
|
|
image: postgres:17-alpine
|
|
networks:
|
|
default:
|
|
aliases: [task-stream-postgres]
|
|
environment:
|
|
POSTGRES_USER: taskstream
|
|
POSTGRES_PASSWORD: taskstream
|
|
POSTGRES_DB: taskstream
|
|
volumes: [postgres-data:/var/lib/postgresql/data]
|
|
healthcheck:
|
|
test: ['CMD-SHELL', 'pg_isready -U taskstream']
|
|
interval: 5s
|
|
retries: 10
|
|
|
|
mongo:
|
|
image: mongo:8
|
|
networks:
|
|
default:
|
|
aliases: [task-stream-mongo]
|
|
volumes: [mongo-data:/data/db]
|
|
healthcheck:
|
|
test: ['CMD', 'mongosh', '--quiet', '--eval', 'db.adminCommand("ping")']
|
|
interval: 5s
|
|
retries: 10
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
networks:
|
|
default:
|
|
aliases: [task-stream-redis]
|
|
command: ['redis-server', '--appendonly', 'yes']
|
|
volumes: [redis-data:/data]
|
|
healthcheck:
|
|
test: ['CMD', 'redis-cli', 'ping']
|
|
interval: 5s
|
|
retries: 10
|
|
|
|
volumes:
|
|
postgres-data:
|
|
mongo-data:
|
|
redis-data:
|
|
|
|
secrets:
|
|
nexus_token:
|
|
environment: NEXUS_TOKEN
|