* Include k8s deployment instructions * Use helm for deployment * docs(self-host): add Helm / Kubernetes deployment to quickstart (en + zh) * fix(helm): gate backend ExternalName alias behind a value The unprefixed Service/backend in the chart is load-bearing, but as written it limits the chart to one release per namespace and fails helm install whenever a Service/backend already exists in the namespace (without --take-ownership). Gate the alias behind frontend.compatibility.backendAlias (default true, so existing installs are unchanged). Operators running a web image with a patched REMOTE_API_URL can set it to false to drop the Service entirely. Document the one-release-per-namespace constraint and the opt-out in values.yaml and the SELF_HOSTING.md Kubernetes section. Addresses review item #1 on PR #2377. * fix(helm): add backend startupProbe so cold installs survive migrations The entrypoint runs `./migrate up` before serving traffic. On a cold cluster (Postgres still coming up) this can take minutes, during which the livenessProbe (initialDelaySeconds 30 / periodSeconds 30) trips and restarts the pod 1-2 times. Add a startupProbe on /healthz (failureThreshold 30, periodSeconds 10, ~5 min budget). Kubernetes disables liveness/readiness until it passes, so migrations finish without the pod being killed, and the aggressive livenessProbe is untouched for steady-state. Update the SELF_HOSTING.md install step, which no longer expects 1-2 restarts. Addresses review item #2 on PR #2377. * fix(helm): roll backend pods on config/secret change via checksum annotations envFrom does not watch the referenced ConfigMap/Secret, and helm upgrade alone does not change the pod template hash, so editing values.yaml + `helm upgrade` left the old backend pods running stale config. Add checksum/config (hash of the rendered configmap.yaml) and checksum/secret (hash of the live existingSecret via lookup, since it is created out-of-band and has no chart template) to the backend pod template. Config edits now actually re-roll the backend on upgrade, and Secret rotations do too. lookup is empty under `helm template`/`--dry-run`; that placeholder is harmless and documented inline. Addresses review item #3 on PR #2377. * docs(self-host): sync quickstart with new startupProbe behavior SELF_HOSTING.md was updated to reflect that the backend now stays Running but not Ready while Postgres comes up (startupProbe absorbs it, so no restart), but the EN/ZH quickstart docs still described the pre-startupProbe behavior of "may restart 1-2 times". Bring them in line. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan Jiang <52446949+Bohan-J@users.noreply.github.com> Co-authored-by: multica-agent <github@multica.ai>
15 KiB
Self-Hosting Guide
Deploy Multica on your own infrastructure in minutes.
Architecture
| Component | Description | Technology |
|---|---|---|
| Backend | REST API + WebSocket server | Go (single binary) |
| Frontend | Web application | Next.js 16 |
| Database | Primary data store | PostgreSQL 17 with pgvector |
Each user who runs AI agents locally also installs the multica CLI and runs the agent daemon on their own machine.
Quick Install (Recommended)
Two commands to set up everything — server, CLI, and configuration:
# 1. Install CLI + provision the self-host server
curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash -s -- --with-server
# 2. Configure CLI, authenticate, and start the daemon
multica setup self-host
This installs the multica CLI, checks out the latest self-host assets, pulls the official Multica images from GHCR, and configures everything for localhost.
Open http://localhost:3000. To log in, configure RESEND_API_KEY in .env for email-based codes (recommended), or leave Resend unset and copy the generated code from the backend logs. See Step 2 — Log In for details.
Prerequisites: Docker and Docker Compose must be installed. The script checks for this and provides install links if missing.
CLI only? If the self-host server is already running and you only need the CLI on a macOS/Linux machine, install it with Homebrew:
brew install multica-ai/tap/multica
Step-by-Step Setup (Alternative)
If you prefer to run each step manually:
Step 1 — Start the Server
Prerequisites: Docker and Docker Compose.
git clone https://github.com/multica-ai/multica.git
cd multica
make selfhost
make selfhost automatically creates .env from the example, generates a random JWT_SECRET, and starts all services via Docker Compose.
By default it pulls the latest stable release images from GHCR. To build the backend/web from your current checkout instead, run make selfhost-build.
If the selected GHCR tag has not been published yet, make selfhost now tells you to fall back to make selfhost-build.
make selfhost-build uses local multica-backend:dev / multica-web:dev tags, so it does not overwrite the pulled :latest images.
Once ready:
- Frontend: http://localhost:3000
- Backend API: http://localhost:8080
Note: If you prefer to run the Docker Compose steps manually, see Manual Docker Compose Setup below.
Step 2 — Log In
Open http://localhost:3000 in your browser. The Docker self-host stack defaults to APP_ENV=production (set in docker-compose.selfhost.yml), and there is no fixed verification code by default. Pick one of the following to log in:
- Recommended (production): configure
RESEND_API_KEYin.env, then restart the backend. Real verification codes will be sent to the email address you enter. See Advanced Configuration → Email. - Without email configured: the verification code is generated server-side and printed to the backend container logs (look for
[DEV] Verification code for ...:). Useful for one-off testing on a single machine. - Deterministic local/private testing: set
APP_ENV=developmentandMULTICA_DEV_VERIFICATION_CODE=888888in.env, then restart the backend. This fixed code is ignored whenAPP_ENV=production.
Changes to ALLOW_SIGNUP and GOOGLE_CLIENT_ID also take effect after restarting the backend / compose stack. The web UI reads both from /api/config at runtime, so no web rebuild is needed.
Warning: do not set
MULTICA_DEV_VERIFICATION_CODEon a publicly reachable instance — anyone who knows an email address can then log in with that fixed code.
Step 3 — Install CLI & Start Daemon
The daemon runs on your local machine (not inside Docker). It detects installed AI agent CLIs, registers them with the server, and executes tasks when agents are assigned work.
Each team member who wants to run AI agents locally needs to:
a) Install the CLI and an AI agent
brew install multica-ai/tap/multica
You also need at least one AI agent CLI installed:
- Claude Code (
claudeon PATH) - Codex (
codexon PATH) - GitHub Copilot CLI (
copiloton PATH) - OpenClaw (
openclawon PATH) - OpenCode (
opencodeon PATH) - Hermes (
hermeson PATH) - Gemini (
geminion PATH) - Pi (
pion PATH) - Cursor Agent (
cursor-agenton PATH) - Kimi (
kimion PATH) - Kiro CLI (
kiro-clion PATH)
b) One-command setup
multica setup self-host
This automatically:
- Configures the CLI to connect to
localhost(ports 8080/3000) - Opens your browser for authentication
- Discovers your workspaces
- Starts the daemon in the background
For on-premise deployments with custom domains:
multica setup self-host --server-url https://api.example.com --app-url https://app.example.com
To verify the daemon is running:
multica daemon status
Alternative: If you prefer manual steps, see Manual CLI Configuration below.
Step 4 — Verify & Start Using
- Open your workspace in the web app at http://localhost:3000
- Navigate to Settings → Runtimes — you should see your machine listed
- Go to Settings → Agents and create a new agent
- Create an issue and assign it to your agent — it will pick up the task automatically
Kubernetes Deployment (Alternative)
If you already run a Kubernetes cluster, you can deploy Multica there instead of Docker Compose using the Helm chart at deploy/helm/multica/. It targets a typical k3s / k8s setup with an Ingress controller and a default ReadWriteOnce StorageClass — authored against k3s + Traefik + local-path, and should work on any cluster with minor tweaks.
The chart creates the following resources in the target namespace:
multica-postgres—pgvector/pgvector:pg17backed by a 10Gi PVCmultica-backend— Go API/WS server backed by a 5Gi uploads PVCmultica-frontend— Next.js standalone server- Two
Ingressresources: one for the web host, one for the backend host multica-configConfigMap (rendered fromvalues.yaml)
The multica-secrets Secret is not managed by the chart — you create it once with kubectl so real values never need to land in git.
One release per namespace: the prebuilt
multica-webimage bakesREMOTE_API_URL=http://backend:8080at build time, so the chart ships an ExternalName Service literally namedbackend. Because that name is unprefixed, you can run only one Multica release per namespace, andhelm installwill fail if aService/backendalready exists there (pass--take-ownership, or use a dedicated namespace). If you build a web image with a patchedREMOTE_API_URL, setfrontend.compatibility.backendAlias: falseto drop the alias.
Prerequisites:
kubectlandhelm(v3.13+ for--take-ownership, or v4+) configured for the target cluster, an Ingress controller (Traefik / NGINX), and a default StorageClass.
Step 1 — Point hostnames at the cluster
The chart defaults to multica.dev.lan (web) and api.multica.dev.lan (backend). Pick one of:
-
/etc/hostson every machine that needs access (developer laptops + the machine running the daemon):192.168.1.206 multica.dev.lan api.multica.dev.lanReplace
192.168.1.206with any node IP where your Ingress controller's Service is reachable. -
Local DNS (Pi-hole, Unbound, etc.): add A records for both hostnames pointing at the cluster Ingress IP.
To use different hostnames, override the matching values at install time (see Step 4) — ingress.frontend.host, ingress.backend.host, plus backend.config.appUrl, backend.config.frontendOrigin, backend.config.localUploadBaseUrl, and backend.config.googleRedirectUri.
Step 2 — Create the namespace
kubectl create namespace multica
Step 3 — Create the multica-secrets Secret
The chart references this Secret by name. Create it once with random values:
kubectl -n multica create secret generic multica-secrets \
--from-literal=JWT_SECRET="$(openssl rand -hex 32)" \
--from-literal=POSTGRES_PASSWORD="$(openssl rand -hex 16)" \
--from-literal=RESEND_API_KEY="" \
--from-literal=GOOGLE_CLIENT_SECRET="" \
--from-literal=CLOUDFRONT_PRIVATE_KEY="" \
--from-literal=MULTICA_DEV_VERIFICATION_CODE=""
Leave optional values empty for now — you can fill them in later (see Step 5 — Log In).
Step 4 — Install the chart
helm install multica deploy/helm/multica -n multica
To override defaults, copy deploy/helm/multica/values.yaml, edit it, and pass it with -f:
cp deploy/helm/multica/values.yaml my-values.yaml
# edit my-values.yaml — e.g. change ingress hosts, image tags, resource limits
helm install multica deploy/helm/multica -n multica -f my-values.yaml
Watch the pods come up:
kubectl -n multica get pods -w
On a cold cluster the backend can sit Running but not Ready for a few minutes while it waits on PostgreSQL and runs migrations — a startupProbe absorbs this, so the pod should not restart. Once the backend reports Ready, migrations have completed and /healthz returns OK:
curl -H "Host: api.multica.dev.lan" http://<ingress-ip>/healthz
# {"status":"ok","checks":{"db":"ok","migrations":"ok"}}
Then open http://multica.dev.lan in your browser.
Step 5 — Log In
The chart defaults to APP_ENV=production (set in values.yaml under backend.config.appEnv), and there is no fixed verification code by default. Pick one of the following to log in — the same three options as the Docker setup:
-
Recommended (production): patch the Secret with a real Resend key, then restart the backend:
kubectl -n multica patch secret multica-secrets --type=merge \ -p '{"stringData":{"RESEND_API_KEY":"re_xxx"}}' kubectl -n multica rollout restart deploy/multica-backendReal verification codes will be sent to the email address you enter. See Advanced Configuration → Email.
-
Without email configured: the verification code is generated server-side and printed to the backend pod logs (look for
[DEV] Verification code for ...:). Useful for one-off testing.kubectl -n multica logs -f deploy/multica-backend | grep "Verification code" -
Deterministic local/private testing: set
backend.config.appEnv: developmentin your values file andMULTICA_DEV_VERIFICATION_CODE=888888in the Secret, thenhelm upgradeand restart. This fixed code is ignored whenAPP_ENV=production.helm upgrade multica deploy/helm/multica -n multica \ -f my-values.yaml --set backend.config.appEnv=development kubectl -n multica patch secret multica-secrets --type=merge \ -p '{"stringData":{"MULTICA_DEV_VERIFICATION_CODE":"888888"}}' kubectl -n multica rollout restart deploy/multica-backend
ALLOW_SIGNUP and GOOGLE_CLIENT_ID likewise live under backend.config.* in values.yaml. After helm upgrade, the backend pod will roll automatically because the ConfigMap hash changes; the web UI reads both from /api/config at runtime, so no web rebuild is needed.
Warning: do not set
MULTICA_DEV_VERIFICATION_CODEon a publicly reachable instance — anyone who knows an email address can then log in with that fixed code.
Step 6 — Install CLI & Start Daemon
The daemon runs on your local machine, not in the cluster. Install the CLI and an AI agent as in Step 3 above, then point the CLI at your Ingress hostnames:
multica setup self-host \
--server-url http://api.multica.dev.lan \
--app-url http://multica.dev.lan
Make sure the machine running the daemon has the same /etc/hosts (or DNS) entries from Step 1.
Updating
To pull the latest images without changing the chart version:
kubectl -n multica rollout restart deploy/multica-backend deploy/multica-frontend
To pin a specific Multica release, set the image tags in your values file:
images:
backend:
tag: v0.2.4
frontend:
tag: v0.2.4
Then upgrade:
helm upgrade multica deploy/helm/multica -n multica -f my-values.yaml
To roll back if an upgrade goes sideways:
helm -n multica rollback multica
Tearing down
# Remove the workloads but keep the PVCs and the Secret
helm -n multica uninstall multica
# Wipe everything, including PostgreSQL data and uploads
kubectl delete namespace multica
Stopping Services
If you installed via the install script:
curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash -s -- --stop
If you cloned the repo manually:
# Stop the Docker Compose services (backend, frontend, database)
make selfhost-stop
# Stop the local daemon
multica daemon stop
Switching to Multica Cloud
If you've been self-hosting and want to switch your CLI to Multica Cloud:
multica setup
This reconfigures the CLI for multica.ai, re-authenticates, and restarts the daemon. You will be prompted before overwriting the existing configuration.
Your local Docker services are unaffected. Stop them separately if you no longer need them.
Upgrading
docker compose -f docker-compose.selfhost.yml pull
docker compose -f docker-compose.selfhost.yml up -d
Pin MULTICA_IMAGE_TAG in .env to an exact version like v0.2.4 if you want to stay on a specific release. Migrations run automatically on backend startup.
If the selected GHCR tag has not been published yet, fall back to make selfhost-build or docker compose -f docker-compose.selfhost.yml -f docker-compose.selfhost.build.yml up -d --build.
Manual Docker Compose Setup
If you prefer running Docker Compose steps manually instead of make selfhost:
git clone https://github.com/multica-ai/multica.git
cd multica
cp .env.example .env
Edit .env — at minimum, change JWT_SECRET:
JWT_SECRET=$(openssl rand -hex 32)
Then start everything:
docker compose -f docker-compose.selfhost.yml pull
docker compose -f docker-compose.selfhost.yml up -d
Manual CLI Configuration
If you prefer configuring the CLI step by step instead of multica setup:
# Point CLI to your local server
multica config set server_url http://localhost:8080
multica config set app_url http://localhost:3000
# Login (opens browser)
multica login
# Start the daemon
multica daemon start
For production deployments with TLS:
multica config set app_url https://app.example.com
multica config set server_url https://api.example.com
multica login
multica daemon start
Advanced Configuration
For environment variables, manual setup (without Docker), reverse proxy configuration, database setup, and more, see the Advanced Configuration Guide.