Vibe Coding
Back to Multica

Multica / Get started

Get started quickly with Self-Host

Use Docker to run Multica on your own server or local machine (you can also use Helm on Kubernetes). About 10 minutes.

Get started quickly with Self-Host key concepts infographic
Get started quickly with Self-Host key concepts infographic

Overview

This page takes you through using Docker to run Multica's server (backend + frontend + PostgreSQL) on your own machine or server. After completing this article, your data is completely in your own hands - including workspace, issue, comments, and agent configuration.

The execution of the agent still relies on your locally run daemon process + locally installed AI programming tools - this is exactly the same as Cloud. Self-host replaces the server layer, not the execution layer.

Prerequisites

  • Docker is installed and can run docker compose
  • Git optional (recommended - you can pull source code)
  • A machine that can be turned on for a long time (either local/intranet/cloud host)
  • At least one AI programming tool installed on the machine running the daemon (not necessarily the machine running the server - it can be your development laptop)

1. Pull the project + start the backend with one click

make selfhost will:

**Already have a Kubernetes cluster? ** Instead of using Docker, use Helm chart directly - jump to [Kubernetes deployment (alternative)] (#kubernetes-deployment alternative) below, and then return to [Step 4] (#4-First login--Create workspace) to complete the login.

git clone https://github.com/multica-ai/multica.git
cd multica
make selfhost
  1. If there is no .env file, automatically generate one from .env.example and generate random JWT_SECRET and Postgres password
  2. Pull the official Docker image (PostgreSQL, Multica backend, Multica frontend)
  3. Use docker-compose.selfhost.yml to start all services
  4. Wait for the backend /health endpoint to be ready

If it is a production probe after startup and you want database or migration exceptions to be reflected as failures, please use /readyz instead.

When the backend container starts, it will automatically run database migration (docker/entrypoint.sh executes ./migrate up before starting the server) - you will see the migration output in the backend log. Upgrading the version is also handled automatically.

**Is the image not released yet? ** If make selfhost reports that it cannot pull the image, it may be that you are on an unreleased version tag. Switch to a stable version or build directly from source: make selfhost-build.

After startup is complete:

**All ports only listen to 127.0.0.1. ** docker-compose.selfhost.yml binds each published port to loopback - ss -tlnp will not see 0.0.0.0:8080, and the external network/other machines will not be connected at all by default. This is to avoid service keys and Postgres credentials being exposed directly to the public network. To do cross-machine access, please use a reverse proxy to terminate TLS in front, as detailed below [Step 5b - Cross-machine access: Use a reverse proxy to block the service in front] (#5b-Cross-machine access, use a reverse proxy to block the service in front).

2. Important: Maintain production security configurations

docker-compose.selfhost.yml sets APP_ENV to production by default and leaves MULTICA_DEV_VERIFICATION_CODE empty, so the public network instance does not have a fixed verification code by default. Only set MULTICA_DEV_VERIFICATION_CODE in local or private test automation. If fixed verification codes are enabled when APP_ENV is not in production, anyone who can request a verification code can log in with this fixed value. For details, see [Login and Registration Configuration → Fixed Local Test Verification Code] (/auth-setup#Fixed Local Test Verification Code). Be sure to check APP_ENV=production in .env before deploying on the public network, and MULTICA_DEV_VERIFICATION_CODE is empty.

4. First time login + create workspace

Open http://localhost:3000:

  • Enter your email
  • Get the verification code from the email received by your configured email backend (Resend or SMTP relay); if neither is configured, copy the line [DEV] Verification code from the stdout of the server container
  • Do not use 888888 directly; it will only take effect if MULTICA_DEV_VERIFICATION_CODE=888888 is explicitly set on a non-production private instance
  • Create your first workspace after logging in

5. Connect the command line tool to your own server

The command line installation method is the same as Cloud Quick Start → 2. Install command line tools - Homebrew / Script / PowerShell optional.

5a. The same machine

When the CLI and server are on the same machine, the default parameters are sufficient:

multica setup self-host

It will automatically connect to http://localhost:8080 (backend) + http://localhost:3000 (frontend), guide you to log in in the browser, save PAT locally, and automatically start the daemon process.

5b. Cross-machine access: Use reverse proxy to block services in front

Because compose only listens to 127.0.0.1 by default, daemons running from other machines cannot connect to http://<server-ip>:8080 - this is also intentional, otherwise the service key will be directly exposed to the public network. The correct approach is to run a reverse proxy (Caddy / nginx / Cloudflare Tunnel) on the server, let it terminate TLS, and then reverse proxy to 127.0.0.1:8080 (backend) and 127.0.0.1:3000 (frontend). Then point the CLI to the public HTTPS domain name:

multica setup self-host \
  --server-url https://<your domain name> \
  --app-url https://<your domain name>

Are you more comfortable using environment variables? When the corresponding flag is omitted, setup self-host will read MULTICA_SERVER_URL and MULTICA_APP_URL (when set at the same time, the flag takes precedence). MULTICA_SERVER_URL also accepts the ws://.../ws daemon writing method in Environment Variables, and automatically normalizes it to an HTTP address.

The smallest available Caddyfile, a single domain name with both front and back ends (with WebSocket forwarding, both daemon and web page depend on it):

multica.example.com {
    # WebSocket routing - must precede catch-all
    @ws path /ws /ws/*
    handle @ws {
        reverse_proxy 127.0.0.1:8080 {
            flush_interval -1
        }
    }

    #BackendAPI
    handle /api/* {
        reverse_proxy 127.0.0.1:8080
    }

    # Other requests → Frontend
    reverse_proxy 127.0.0.1:3000
}

After the proxy is up, remember to set FRONTEND_ORIGIN in the server's .env to https://multica.example.com and restart the backend, otherwise the origin verification of WebSocket will reject the browser (see [Troubleshooting → WebSocket cannot connect](/vibe-coding-tools/multica/troubleshooting#websocket-cannot connect)).

Cloudflare Tunnel is also a good choice - it directly gives a public domain name + TLS, and there is no need to expose any ports on the host to the outside world. Nginx can also do it (divided into two domain names app. / api. + proxy_set_header Upgrade to WebSocket). The key is to terminate TLS and forward the Upgrade header on /ws.

6. Create the agent + assign the first task

The process is the same as Cloud - see Getting Started Quickly with Cloud → Steps 5-6.

<span id="7-usage-rollup-no-operator-action-required" />

7. Usage summary (no operation and maintenance required)

The in-process scheduler ticks every 30 seconds and claims the 5-minute UTC plan through the syscronexecutions table. It is safe to run multiple backend copies at the same time - the unique key (jobname, scopekind, scopeid, plantime) ensures that there is only one winner for each plan. New deployments do not require any additional configuration.

Usage / Runtime Kanban reads the derived table task_usage_hourly, which is filled periodically by rollup_task_usage_hourly(). As of MUL-2957, the backend runs this rollup in-process via the DB backend's scheduler - pg_cron is no longer required and an external cron / systemd timer is no longer recommended. The default pgvector/pgvector:pg17 image works without modification.

**Compatibility - Registered pg_cron task. ** If you have previously registered rollup with pg_cron (SELECT cron.schedule('rollup_task_usage_hourly', '*/5 * * * *', …)), it is okay not to delete it - the SQL function internally holds advisory lock 4246, and the application scheduler and pg_cron will not concurrently double write. To clear redundant items, you can execute:

SELECT cron.unschedule('rollup_task_usage_hourly')
  FROM cron.job WHERE jobname = 'rollup_task_usage_hourly';

**Upgraded from v0.3.4 → v0.3.5+. ** The previous version required operations to manually run cmd/backfill_task_usage_hourly before applying migration 103, otherwise the fail-closed guard would abort migrate up. As of MUL-2957 this step is automatic: the migrate command runs an idempotent monthly slice backfill before applying migration 103 (protected by advisory lock 4246) before continuing. On a busy database you can still run a standalone backfill with --sleep-between-slices=2s to limit read pressure, but it is no longer necessary.

For complete reference (including operation and maintenance considerations and Kubernetes deployment forms), see SELF_HOSTING_ADVANCED.md → Usage Dashboard Rollup in the repository.

Kubernetes deployment (alternative)

If you are already running a Kubernetes cluster, a Helm chart is also included in the repository, with the path deploy/helm/multica/. It is the k8s version of make selfhost - the same backend image, frontend image, pgvector/pgvector:pg17 Postgres, encapsulated into Deployment / Service / Ingress, plus a ConfigMap rendered by values.yaml. This set of charts is based on k3s + Traefik + local-path

This chart does not template any sensitive values. It refers to a Secret called multica-secrets by name, so the real JWT / DB / Resend / Google keys never need to be entered in git, nor in values.yaml. First use kubectl to create the namespace and Secret at once:

kubectl create namespace multica

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=""

Reinstall chart:

git clone https://github.com/multica-ai/multica.git
cd multica
helm install multica deploy/helm/multica -n multica

The default hostnames are multica.dev.lan (web) and api.multica.dev.lan (backend). Just add them to /etc/hosts (or local DNS) and point them to any node IP that is reachable by Ingress. To change the host name, make a copy of deploy/helm/multica/values.yaml, change ingress.frontend.host / ingress.backend.host, then change backend.config.appUrl / frontendOrigin / localUploadBaseUrl / googleRedirectUri to the corresponding address, and then helm install ... -f my-values.yaml.

On a cold cluster, the backend may be Running but Not Ready for a few minutes until Postgres gets up and runs the migration - startupProbe will capture this period and the pod will not be restarted by liveness. Wait for it to be Ready:

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 the browser and return to the above [Step 4 - First Login] (#4 - First Login - Create Workspace) to continue. Connect to your Ingress host from the command line:

multica setup self-host \
  --server-url http://api.multica.dev.lan \
  --app-url http://multica.dev.lan

I only want to pull the latest image without moving the chart: kubectl -n multica rollout restart deploy/multica-backend deploy/multica-frontend. To lock to a certain Multica version, set images.backend.tag / images.frontend.tag in the values ​​file and then helm upgrade. helm -n multica uninstall multica only deletes the workload, keeping both PVC and Secret; kubectl delete namespace multica will clear everything.

The complete reference - the three login methods, the backend ExternalName alias added to bypass the REMOTE_API_URL hard-coded in the web image build-time, resource restrictions, TLS - are all in the repository SELF_HOSTING.md.

FAQ

  • The backend cannot start: Look at the container log docker compose -f docker-compose.selfhost.yml logs backend; the common problem is DATABASE_URL or JWT_SECRET in .env
  • Verification code not received: No email backend is configured (Neither Resend nor SMTP is set) → Find [DEV] Verification code from docker compose logs backend
  • WebSocket cannot connect: Public network deployment must set FRONTEND_ORIGIN to your real front-end domain name; see [Troubleshooting → WebSocket cannot connect](/vibe-coding-tools/multica/troubleshooting#websocket-cannot connect)
  • Usage / Runtime Kanban is always 0: No one schedules rollup_task_usage_hourly() - see Step 7 above and [Troubleshooting → Usage Kanban is always 0](/vibe-coding-tools/multica/troubleshooting#usage-kanban is always -0)
  • migrate up reports refusing to drop legacy daily rollups: fail-closed guard for v0.3.4 → v0.3.5+ upgrade path. As of MUL-2957 the migrate command will automatically run backfill before applying migration 103 - see Step 7

Next step