-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathdocker-compose.cloud.yml
More file actions
206 lines (198 loc) · 9 KB
/
Copy pathdocker-compose.cloud.yml
File metadata and controls
206 lines (198 loc) · 9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# =============================================================================
# AnythingMCP Cloud — Managed SaaS Deployment
# =============================================================================
# Usage: docker compose -f docker-compose.cloud.yml up -d
# Domain: cloud.anythingmcp.com
# =============================================================================
name: amcp-cloud
services:
# Caddy Reverse Proxy — automatic HTTPS via Let's Encrypt
caddy:
image: caddy:2-alpine
container_name: amcp-cloud-caddy
ports:
- "80:80"
- "443:443"
environment:
- DOMAIN=${DOMAIN}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
app:
condition: service_healthy
restart: unless-stopped
# NestJS Backend + Next.js Frontend (single container)
app:
image: helpcodeai/anythingmcp:latest
container_name: amcp-cloud-app
expose:
- "3000"
- "4000"
environment:
- NODE_ENV=production
- PORT=4000
# V8 old-space cap for the backend, read by start.sh. Left unset it
# defaults to 2048, which suits a 4 GB host; the tool registry outgrew
# that and crash-looped the instance on 10 Sep. Raise it in the server
# .env when the host has the RAM to back it — roughly half of total is a
# safe ceiling, since postgres, redis, motis and caddy share the box.
- NODE_MAX_OLD_SPACE_MB=${NODE_MAX_OLD_SPACE_MB:-2048}
- DEPLOYMENT_MODE=cloud
- NEXT_PUBLIC_API_URL=https://${DOMAIN}
- DATABASE_URL=postgresql://amcp:${POSTGRES_PASSWORD}@postgres:5432/anythingmcp
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
# Shared secret for the onboarding-reminders GitHub Actions cron.
# Must match the repo secret ONBOARDING_CRON_SECRET. Unset = cron
# endpoint refuses all calls (self-host default).
- CRON_SECRET=${CRON_SECRET:-}
# Shared secret presented to anythingmcp.com as x-amcp-service-token when
# this backend asks for a licence on a user's behalf. The licence API
# rate-limits anonymous callers per IP, and every cloud trial request
# leaves from this one droplet, so without it the whole cloud shared a
# bucket of three trials an hour. Must match LICENSE_SERVICE_TOKEN on the
# website. Unset = public limit (self-host default).
- LICENSE_SERVICE_TOKEN=${LICENSE_SERVICE_TOKEN:-}
- CORS_ORIGIN=https://${DOMAIN}
- SERVER_URL=https://${DOMAIN}
- FRONTEND_URL=https://${DOMAIN}
- MCP_AUTH_MODE=${MCP_AUTH_MODE:-oauth2}
# MCP Streamable HTTP response framing. `true` = single-shot
# `application/json` responses; `false` = SSE-framed `text/event-stream`.
# Microsoft Copilot Studio's MCP client cannot deserialize SSE-framed
# responses ("response could not be deserialized as JSON") and requires
# plain JSON, so the cloud defaults to JSON. This is spec-compliant and
# every compliant client (incl. Claude) handles it; combined with the
# default stateless mode (MCP_STATEFUL_SESSIONS unset) it also avoids the
# `-32001 Session not found` errors Copilot hits against stateful servers.
- MCP_STREAMABLE_JSON_RESPONSE=${MCP_STREAMABLE_JSON_RESPONSE:-true}
- ALLOW_OPEN_REGISTRATION=${ALLOW_OPEN_REGISTRATION:-true}
- LICENSE_API_URL=${LICENSE_API_URL:-https://anythingmcp.com}
# Operator analytics — cloud build only. Leave unset on self-hosted.
- GTM_ID=${GTM_ID:-}
- COOKIE_DOMAIN=${COOKIE_DOMAIN:-}
# Proxy / web-unblocker (e.g. Zyte API proxy mode). Tools with
# use_proxy=true route through this; unset = feature off everywhere.
- CONNECTOR_PROXY_URL=${CONNECTOR_PROXY_URL:-}
- PROXY_RATE_LIMIT_DEFAULT=${PROXY_RATE_LIMIT_DEFAULT:-100}
# Internal MOTIS instance for the Deutsche Bahn connector. When set, the
# adapter's MOTIS_URL is filled in automatically at import (and hidden
# from the install form), so cloud users never see or choose it.
# Self-hosters leave it unset and enter their own MOTIS URL.
- MOTIS_INTERNAL_URL=${MOTIS_INTERNAL_URL:-http://motis:8080}
# Allow the SSRF guard to reach the internal MOTIS host (it resolves to
# a private docker IP, which the guard blocks by default). Harmless on
# self-host (no such host on their network).
- SSRF_ALLOWED_HOSTS=${SSRF_ALLOWED_HOSTS:-motis}
# Knowledge Graph — AI (LLM) features. Off unless KG_LLM_ENABLED=true AND a
# provider key are set; the per-workspace toggle still gates actual usage.
- KG_LLM_ENABLED=${KG_LLM_ENABLED:-false}
- KG_LLM_PROVIDER=${KG_LLM_PROVIDER:-}
- KG_LLM_MODEL=${KG_LLM_MODEL:-}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
# Domain-verification token for the OpenAI plugin directory; served at
# /.well-known/openai-apps-challenge. Unset = route answers 404.
- OPENAI_APPS_CHALLENGE_TOKEN=${OPENAI_APPS_CHALLENGE_TOKEN:-}
- KG_LLM_REDACT_INTENTS=${KG_LLM_REDACT_INTENTS:-true}
# System (operator) transactional-email fallback — used when a workspace
# hasn't set its own SMTP (e.g. Resend). Read only by the email sender,
# never exposed to workspace admins. Set these in the droplet .env.
- SMTP_HOST=${SMTP_HOST:-}
- SMTP_PORT=${SMTP_PORT:-587}
- SMTP_USER=${SMTP_USER:-}
- SMTP_PASS=${SMTP_PASS:-}
- SMTP_FROM=${SMTP_FROM:-}
- SMTP_SECURE=${SMTP_SECURE:-false}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:4000/health"]
interval: 10s
timeout: 5s
retries: 5
# Cold start loads the full connector/tool catalog (~18.5k tools, ~90s).
# start_period must comfortably exceed that: during it, failing probes keep
# the container in "starting" (not "unhealthy"), so `docker compose --wait`
# in deploy-cloud.yml keeps waiting and the first healthy probe (~90s) turns
# the deploy green. Too short (was 30s) → the container flipped to unhealthy
# mid-boot and --wait aborted, failing a deploy that was actually coming up
# fine (false negative). A genuinely dead app never becomes healthy, so once
# start_period elapses it still goes unhealthy and the deploy fails for real.
start_period: 180s
restart: unless-stopped
# PostgreSQL Database
postgres:
image: postgres:17-alpine
container_name: amcp-cloud-postgres
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=amcp
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=anythingmcp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U amcp -d anythingmcp"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
# Redis Cache — caching & rate limiting
redis:
image: redis:7-alpine
container_name: amcp-cloud-redis
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
# MOTIS — routing engine behind the Deutsche Bahn connector, fed by the open
# gtfs.de timetable (CC BY 4.0) plus its GTFS-RT feed for live delays.
# Replaces db-rest, which scraped bahn.de and died when Deutsche Bahn began
# blocking datacenter IPs (see deploy/motis/README.md).
# INTERNAL ONLY: deliberately NO `ports:` mapping → reachable solely as
# http://motis:8080 from the app over the amcp-cloud_default network, never
# from the internet. Do NOT add a `ports:` entry or a Caddy route.
motis:
# Custom image: official MOTIS + an entrypoint that downloads and imports
# the feeds and refreshes them weekly (deploy/motis). The tag is pinned so
# the image persists across deploys and is only rebuilt when it changes.
build:
context: ./deploy/motis
image: anythingmcp-motis:2.11.3-1
container_name: amcp-cloud-motis
expose:
- "8080"
volumes:
- motis_data:/data
environment:
- MOTIS_REFRESH_DAYS=${MOTIS_REFRESH_DAYS:-7}
healthcheck:
# 127.0.0.1, not localhost: MOTIS binds IPv4 only and localhost resolves
# to ::1 first inside the container, so the probe was refused every time.
# /metrics, not /: MOTIS answers 404 on the root, which --spider treats as
# a failure. Between the two, the container reported unhealthy for its
# whole life while serving traffic perfectly.
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:8080/metrics"]
interval: 30s
timeout: 5s
retries: 3
# First boot downloads ~12 MB of GTFS and imports it (seconds, not
# minutes); the margin is for a slow gtfs.de.
start_period: 180s
restart: unless-stopped
volumes:
postgres_data:
redis_data:
motis_data:
caddy_data:
caddy_config: