forked from multica-ai/multica
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.selfhost.yml
More file actions
234 lines (229 loc) · 12.7 KB
/
Copy pathdocker-compose.selfhost.yml
File metadata and controls
234 lines (229 loc) · 12.7 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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# Self-hosting Docker Compose — starts PostgreSQL, backend, and frontend.
#
# Services bind to 127.0.0.1 only. For cross-machine or public access, front
# them with a reverse proxy (Caddy / nginx / Cloudflare Tunnel) that terminates
# TLS and forwards to 127.0.0.1:8080 (backend) and 127.0.0.1:3000 (frontend).
# Do NOT change these bindings to 0.0.0.0 — Docker bypasses host firewalls
# (UFW/iptables) by default, so the raw ports would be exposed to the internet
# while any deployment credentials remain weak. See:
# apps/docs/content/docs/self-host-quickstart.mdx
#
# Usage:
# cp .env.example .env
# # Edit .env — generate JWT_SECRET with `openssl rand -hex 32` (required)
# docker compose -f docker-compose.selfhost.yml up -d
#
# Frontend: http://localhost:${FRONTEND_PORT:-3000}
# Backend: http://localhost:${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}
#
# The published values above are HOST ports; the containers always listen on
# 8080 / 3000 internally, so changing them never needs a rebuild. PORT is the
# variable to edit; BACKEND_PORT, API_PORT and SERVER_PORT are optional aliases
# that override it in that order. Keep this alias order identical to Makefile
# and scripts/local-env.sh. The web dev fallback intentionally omits PORT
# because Next uses that variable for its own frontend listener.
#
# Note that *which source* wins differs per entry point — Compose lets the
# calling environment outrank this file, while make lets the included env file
# outrank the environment. Nothing re-derives the published port from the inputs
# any more: `make selfhost` (via scripts/selfhost-wait.sh) and both installers
# read it back with `docker compose port`, so the health check and the printed
# URL always match what was actually published.
name: multica
services:
postgres:
image: pgvector/pgvector:pg17
environment:
POSTGRES_DB: ${POSTGRES_DB:-multica}
POSTGRES_USER: ${POSTGRES_USER:-multica}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-multica}
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${POSTGRES_USER:-multica} -d ${POSTGRES_DB:-multica}",
]
interval: 5s
timeout: 5s
retries: 5
backend:
image: ${MULTICA_BACKEND_IMAGE:-ghcr.io/multica-ai/multica-backend}:${MULTICA_IMAGE_TAG:-latest}
depends_on:
postgres:
condition: service_healthy
ports:
- "127.0.0.1:${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}:8080"
volumes:
- backend_uploads:/app/data/uploads
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-multica}:${POSTGRES_PASSWORD:-multica}@postgres:5432/${POSTGRES_DB:-multica}?sslmode=disable
DATABASE_SEARCH_WORK_MEM_MB: ${DATABASE_SEARCH_WORK_MEM_MB:-64}
DATABASE_REPLICA_URL: ${DATABASE_REPLICA_URL:-}
DATABASE_REPLICA_MAX_CONNS: ${DATABASE_REPLICA_MAX_CONNS:-}
DATABASE_REPLICA_MIN_CONNS: ${DATABASE_REPLICA_MIN_CONNS:-}
PORT: "8080"
METRICS_ADDR: ${METRICS_ADDR:-}
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET must be set to a strong random value — generate one with 'openssl rand -hex 32'}
FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:-http://localhost:3000}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
RESEND_API_KEY: ${RESEND_API_KEY:-}
RESEND_FROM_EMAIL: ${RESEND_FROM_EMAIL:-noreply@multica.ai}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-25}
SMTP_USERNAME: ${SMTP_USERNAME:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-}
SMTP_TLS: ${SMTP_TLS:-}
SMTP_TLS_INSECURE: ${SMTP_TLS_INSECURE:-false}
SMTP_EHLO_NAME: ${SMTP_EHLO_NAME:-}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-http://localhost:3000/auth/callback}
S3_BUCKET: ${S3_BUCKET:-}
S3_REGION: ${S3_REGION:-us-west-2}
AWS_ENDPOINT_URL: ${AWS_ENDPOINT_URL:-}
S3_USE_PATH_STYLE: ${S3_USE_PATH_STYLE:-}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
ATTACHMENT_DOWNLOAD_MODE: ${ATTACHMENT_DOWNLOAD_MODE:-auto}
ATTACHMENT_DOWNLOAD_URL_TTL: ${ATTACHMENT_DOWNLOAD_URL_TTL:-30m}
CLOUDFRONT_DOMAIN: ${CLOUDFRONT_DOMAIN:-}
CLOUDFRONT_KEY_PAIR_ID: ${CLOUDFRONT_KEY_PAIR_ID:-}
CLOUDFRONT_PRIVATE_KEY: ${CLOUDFRONT_PRIVATE_KEY:-}
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-}
APP_ENV: ${APP_ENV:-production}
MULTICA_DEV_VERIFICATION_CODE: ${MULTICA_DEV_VERIFICATION_CODE:-}
MULTICA_APP_URL: ${MULTICA_APP_URL:-http://localhost:3000}
MULTICA_DATABASE_STARTUP_TIMEOUT: ${MULTICA_DATABASE_STARTUP_TIMEOUT:-3m}
MULTICA_DATABASE_CONNECT_TIMEOUT: ${MULTICA_DATABASE_CONNECT_TIMEOUT:-5s}
MULTICA_SHUTDOWN_HOLD_DURATION: ${MULTICA_SHUTDOWN_HOLD_DURATION:-}
MULTICA_RUNTIME_RECONNECT_GRACE: ${MULTICA_RUNTIME_RECONNECT_GRACE:-}
ALLOW_SIGNUP: ${ALLOW_SIGNUP:-true}
ALLOWED_EMAILS: ${ALLOWED_EMAILS:-}
ALLOWED_EMAIL_DOMAINS: ${ALLOWED_EMAIL_DOMAINS:-}
DISABLE_WORKSPACE_CREATION: ${DISABLE_WORKSPACE_CREATION:-}
# Managed Cloud stays off for self-hosting. All managed Cloud clients use
# this single URL when it is configured.
MULTICA_CLOUD_URL: ${MULTICA_CLOUD_URL:-}
GITHUB_APP_SLUG: ${GITHUB_APP_SLUG:-}
GITHUB_WEBHOOK_SECRET: ${GITHUB_WEBHOOK_SECRET:-}
# GitLab (self-hosted) integration. All four are the opt-in: unset =
# integration disabled. GITLAB_URL is the instance base URL; the OAuth
# client id/secret come from an OAuth application registered on that
# instance; MULTICA_GITLAB_SECRET_KEY (base64-encoded 32-byte key)
# encrypts the per-connection OAuth tokens + webhook secrets at rest.
# The OAuth callback + webhook URLs are derived from MULTICA_PUBLIC_URL.
GITLAB_URL: ${GITLAB_URL:-}
GITLAB_OAUTH_CLIENT_ID: ${GITLAB_OAUTH_CLIENT_ID:-}
GITLAB_OAUTH_CLIENT_SECRET: ${GITLAB_OAUTH_CLIENT_SECRET:-}
MULTICA_GITLAB_SECRET_KEY: ${MULTICA_GITLAB_SECRET_KEY:-}
# App API credentials. The pull_request webhook is what mirrors a PR
# onto a card; these are what let the server authenticate as the App
# and pull the CI / mergeability snapshot that enriches it, plus
# Settings -> Repositories -> Choose from GitHub. Left unset,
# ghsnapshot.NewClientFromEnv returns a nil client and the enrichment
# degrades off silently - no error and no log line to notice.
# GITHUB_APP_PRIVATE_KEY is a PEM block: double-quote it in .env so
# its newlines survive into the container.
GITHUB_APP_ID: ${GITHUB_APP_ID:-}
GITHUB_APP_PRIVATE_KEY: ${GITHUB_APP_PRIVATE_KEY:-}
# Public URL the API is reachable at from the open internet, no
# trailing slash. Used to mint absolute webhook URLs for autopilot
# webhook triggers. Leave unset behind a same-origin reverse proxy
# (e.g. plain localhost dev); the frontend will compose the URL
# from window.origin + webhook_path in that case. Headers are
# intentionally NOT used to derive this value, to avoid Host /
# X-Forwarded-Host spoofing on misconfigured proxies.
MULTICA_PUBLIC_URL: ${MULTICA_PUBLIC_URL:-}
# Surface HTML must use a second, cookie-free browser origin routed to
# this backend. Both settings are optional only in the sense that leaving
# either empty disables plugin surfaces rather than weakening isolation.
MULTICA_PLUGIN_SECRET_KEY: ${MULTICA_PLUGIN_SECRET_KEY:-}
MULTICA_PLUGIN_SURFACE_ORIGIN: ${MULTICA_PLUGIN_SURFACE_ORIGIN:-}
MULTICA_PLUGIN_API_URL: ${MULTICA_PLUGIN_API_URL:-}
MULTICA_PLUGIN_DIR: ${MULTICA_PLUGIN_DIR:-}
# Optional API URL shown in self-host daemon setup commands. Set this
# when the daemon reaches the API through a different URL than the one
# used for public webhook URLs; otherwise MULTICA_PUBLIC_URL is used.
MULTICA_DAEMON_SERVER_URL: ${MULTICA_DAEMON_SERVER_URL:-}
# Comma-separated CIDRs whose source IP is allowed to set
# X-Forwarded-For / X-Real-IP for the webhook per-IP rate limiter.
# Empty default = headers ignored, RemoteAddr used. Set e.g.
# "127.0.0.1/32" when running behind a same-host reverse proxy.
MULTICA_TRUSTED_PROXIES: ${MULTICA_TRUSTED_PROXIES:-}
# Server-side LLM layer, backing internal helpers such as chat title
# generation. Both the API key and the base URL empty = layer disabled,
# and callers fall back silently rather than failing. MULTICA_LLM_MAX_RETRIES
# is the retry budget: unset = 2, 0 = disabled, 1-5 = an exact ceiling.
# Anything else fails the boot instead of being silently corrected.
MULTICA_LLM_API_KEY: ${MULTICA_LLM_API_KEY:-}
MULTICA_LLM_BASE_URL: ${MULTICA_LLM_BASE_URL:-}
MULTICA_LLM_DEFAULT_MODEL: ${MULTICA_LLM_DEFAULT_MODEL:-}
MULTICA_LLM_MAX_RETRIES: ${MULTICA_LLM_MAX_RETRIES:-}
# Lark / Feishu bot integration. MULTICA_LARK_SECRET_KEY is the
# opt-in: unset = integration disabled. Mainland 飞书 and international
# Lark are auto-detected per installation and served side by side, so
# the two base-URL knobs should normally stay EMPTY. They are optional
# deployment-wide overrides that force every installation onto one host
# (proxy / mock / single-cloud staging). Upgrading from a setup that
# used https://open.larksuite.com here? The server relabels existing
# installs to region=lark on first boot, then you can clear them.
# See docs/lark-bot-integration.
MULTICA_LARK_SECRET_KEY: ${MULTICA_LARK_SECRET_KEY:-}
MULTICA_LARK_HTTP_BASE_URL: ${MULTICA_LARK_HTTP_BASE_URL:-}
MULTICA_LARK_CALLBACK_BASE_URL: ${MULTICA_LARK_CALLBACK_BASE_URL:-}
# Slack bot integration. MULTICA_SLACK_SECRET_KEY is the opt-in: unset =
# integration disabled. It decrypts the per-installation bot/app tokens,
# which are brought by each workspace via OAuth/BYO and stored encrypted
# in the database, so this single deployment-wide key is all the operator
# needs to set here.
MULTICA_SLACK_SECRET_KEY: ${MULTICA_SLACK_SECRET_KEY:-}
# Mattermost bot integration. Same opt-in model as Slack: unset =
# integration disabled. It decrypts the per-installation bot tokens,
# which are brought by each workspace (BYO paste of server URL + bot
# access token) and stored encrypted in the database.
MULTICA_MATTERMOST_SECRET_KEY: ${MULTICA_MATTERMOST_SECRET_KEY:-}
# Self-hosted Git provider integration (Forgejo / Gitea / GitLab). This
# is a self-host-only feature, so the compose file turns it on by default;
# the managed cloud leaves it unset (off). It still needs a valid
# MULTICA_VCS_SECRET_KEY below to actually work.
MULTICA_VCS_INTEGRATION_ENABLED: ${MULTICA_VCS_INTEGRATION_ENABLED:-true}
# VCS integration at-rest encryption key for token-based providers
MULTICA_VCS_SECRET_KEY: ${MULTICA_VCS_SECRET_KEY:-}
# WeCom smart-bot integration. MULTICA_WECOM_SECRET_KEY is the
# opt-in: unset = integration disabled. It encrypts each installation's
# smart-bot secret at rest. The server needs it to unseal a bot's
# subscribe credentials at connection time, and to seal the secret when
# an installation is created from the WeCom settings tab.
MULTICA_WECOM_SECRET_KEY: ${MULTICA_WECOM_SECRET_KEY:-}
# Comma-separated ranges the inbound media fetcher may dial even though
# they look reserved. Empty (the default) keeps the SSRF guard as strict
# as it ships; set it only behind a fake-IP proxy whose pool would
# otherwise get every attachment refused. See .env.example.
MULTICA_WECOM_MEDIA_ALLOW_CIDRS: ${MULTICA_WECOM_MEDIA_ALLOW_CIDRS:-}
# 1 = log every inbound and outbound WeCom frame, including the first
# 120 runes of each message body, so a real-device session can be
# checked against the server afterwards. Off unless set; turn it on only
# for a debugging session and unset it when that session ends, because
# what it records is user message content.
MULTICA_WECOM_TRACE: ${MULTICA_WECOM_TRACE:-}
restart: unless-stopped
frontend:
image: ${MULTICA_WEB_IMAGE:-ghcr.io/multica-ai/multica-web}:${MULTICA_IMAGE_TAG:-latest}
depends_on:
- backend
ports:
- "127.0.0.1:${FRONTEND_PORT:-3000}:3000"
environment:
HOSTNAME: "0.0.0.0"
REMOTE_API_URL: ${REMOTE_API_URL:-http://backend:8080}
DOCS_URL: ${DOCS_URL:-}
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-}
NEXT_PUBLIC_WS_URL: ${NEXT_PUBLIC_WS_URL:-}
restart: unless-stopped
volumes:
pgdata:
backend_uploads: