Self-host quickstart
Start Multica with Docker Compose, log in, and connect your first computer.
Self-hosting Multica has two parts:
| Part | What it runs | Where it lives |
|---|---|---|
| Multica service | Web, API, and PostgreSQL | One machine with Docker installed |
| Computer | The Multica daemon and AI coding tools | The computer developers actually work on |
They can be the same machine or separate ones. Self-hosting replaces only the Multica Cloud part.
This guide uses Docker Compose. For Kubernetes deployments, see the Self-hosting guide in the repository.
Before you start
The machine running the Multica service needs:
- Docker Engine or Docker Desktop, with
docker composeworking - Git, Make, curl, and OpenSSL
- Ports
3000and8080free on the machine
Confirm Docker and Compose are available first:
docker info
docker compose versionMultica uses Compose v2, invoked as docker compose. The legacy docker-compose v1 is not supported.
The computer also needs at least one AI coding tool installed and logged in, such as Claude Code, Codex, or Cursor. The Multica CLI is installed in step 5.
Start Multica
On the machine that will run the service:
git clone --depth 1 https://github.com/multica-ai/multica.git
cd multica
make selfhostRunning make selfhost again reuses the existing .env and volumes; it does not regenerate secrets.
make selfhost pulls published images and does not build the code in your checkout. To test local source, use make selfhost-build.
The published images track the newest release tag, while a plain git clone checks out main, which usually runs ahead. If you build anything from this checkout — the CLI, or images via make selfhost-build — check out the matching release tag first so built binaries and the running containers stay on the same version:
git fetch --tags --depth 1
git checkout $(git tag -l 'v*' --sort=-v:refname | head -1)Confirm the services are ready
Check container status:
docker compose -f docker-compose.selfhost.yml pspostgres should show healthy, and backend and frontend should be running. Then check the backend, the database, and migrations:
curl -fsS http://localhost:8080/readyzThe expected response is:
{"status":"ok","checks":{"db":"ok","migrations":"ok"}}The backend container runs database migrations on every startup before serving; there is no manual migration command to run.
Choose how to access
Open http://localhost:3000 directly. You also won't need to pass any URLs when running multica setup self-host later.
Log in and create a workspace
Open http://localhost:3000 locally, or the https://app.example.com you just configured, and enter your email to request a verification code.
No email service is configured by default. After requesting a code, read it from the backend logs:
docker compose -f docker-compose.selfhost.yml logs backend \
| grep "Verification code"The logs contain a line like:
[DEV] Verification code for [email protected]: 123456Enter the code and create your first workspace. Once Resend or SMTP is configured, codes are sent by email and members no longer need to read container logs — see Auth setup.
Self-hosted deployments default to APP_ENV=production, which disables fixed verification codes. Do not set MULTICA_DEV_VERIFICATION_CODE on a public instance.
Connect a computer
Run the commands below on the computer that runs your AI coding tools, which is not necessarily the server running Docker.
Runs execute with the full permissions of the user running the daemon — they can read and write everything that user can. Run the daemon as a dedicated Unix user, in a container, or in a VM rather than under your personal account. See the security model.
Install the Multica CLI first:
curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bashThen point the CLI at your service. Pick where the Multica service runs:
multica setup self-hostWhen the service is on another machine, pass the two URLs you configured above. The command first checks <server-url>/health, then opens a browser to complete login. After login it stores local credentials and starts the daemon. A Server not reachable error at this step means the probe could not get a 200 from /health. There are three likely causes: the proxy forwards everything to a web release that does not forward the path; the proxy does not route /health to the backend at all; or a lower-level failure (DNS, TLS, firewall, timeout, 5xx from the backend). See Troubleshooting.
Confirm the connection:
multica daemon statusThe output should show:
Daemon: runningAgentslisting the AI coding tools installed on this machineWorkspacesgreater than0
Complete the first run
Back in Multica, once an online runtime appears in the runtime list, create an agent and assign it your first issue.
When the run shows as completed and the agent's reply appears in the timeline, the self-hosted service, the computer, and the AI coding tool are all connected. For detailed steps, see steps 3-5 of the quickstart.
Common admin commands
Run all of these from the multica repository directory:
# Check status
docker compose -f docker-compose.selfhost.yml ps
# Tail backend logs
docker compose -f docker-compose.selfhost.yml logs -f backend
# Apply .env changes
docker compose -f docker-compose.selfhost.yml up -d
# Stop the services, keeping the volumes
docker compose -f docker-compose.selfhost.yml downdocker compose down keeps pgdata and backend_uploads. Adding -v deletes these volumes, including the database; do not run docker compose down -v unless you intend to wipe the instance.
Upgrade
Check for a pinned image tag
Both images resolve as ${MULTICA_IMAGE_TAG:-latest} (docker-compose.selfhost.yml, services.backend.image / services.frontend.image), and .env.example ships MULTICA_IMAGE_TAG=latest.
grep MULTICA_IMAGE_TAG .env
# MULTICA_IMAGE_TAG=v0.4.5 ← pinned: edit to `latest` (or the release you want) firstIf you pinned MULTICA_IMAGE_TAG, the upgrade commands below upgrade nothing. pull just re-fetches that same tag and you stay on the old version — no error, no warning.
Back up Postgres
Migrations are forward-only, so take a dump before upgrading a deployment you care about:
docker compose -f docker-compose.selfhost.yml exec -T postgres \
pg_dump -U multica multica > multica-backup.sql && gzip multica-backup.sqlUse your own POSTGRES_USER / POSTGRES_DB from .env if you changed them from the multica defaults. Data lives in the named volume multica_pgdata, which survives docker compose down — but not down -v.
Do not pipe pg_dump straight into gzip. A shell reports the exit status of the last command in a pipeline, so pg_dump … | gzip > backup.sql.gz exits 0 even when the dump failed — leaving a perfectly valid 20-byte archive containing nothing. Redirecting to a file first makes pg_dump's own exit status the one that counts, and && only compresses a dump that actually succeeded.
Pull the new images and recreate the containers
On an existing install both commands produce the same result — the selfhost target in the Makefile runs the same docker compose pull + up -d, and additionally creates .env when it is missing and waits on /health before printing a status summary. Pick whichever you prefer:
cd multica
git pull --ff-only
make selfhostAs in step 2, the backend container runs ./migrate up on startup (docker/entrypoint.sh) before serving traffic. There is no separate upgrade command: bringing up the new image is the migration step. Watch it happen:
docker compose -f docker-compose.selfhost.yml logs -f backendMigrations that backfill historical data (such as 103) complete the backfill automatically too. In rare cases the automatic backfill fails with refusing to drop legacy daily rollups — see Troubleshooting.
Verify with /readyz, not /health
curl -s localhost:8080/readyz
# {"status":"ok","checks":{"db":"ok","migrations":"ok"}}Anything other than HTTP 200 with both checks ok means the new version did not finish migrating — check the backend logs before sending traffic at it.
/health is a liveness probe — it returns {"status":"ok"} as long as the process is up, including when migrations failed. /readyz (readyHandler, registered in server/cmd/server/router.go; /healthz is an alias) checks the database and the applied migration set, so it is the one that catches a bad upgrade.
Common issues
| Symptom | Check first |
|---|---|
/readyz does not return ok | Run docker compose -f docker-compose.selfhost.yml logs backend postgres. |
| No verification code arrives | Request a code, then search the backend logs for Verification code. |
setup self-host reports the server is unreachable | From the computer, request https://api.example.com/health to confirm DNS, TLS, and the reverse proxy are all reachable. A 404 means the proxy sends /health to an older web release that does not forward it — route it to the backend (Troubleshooting). |
The daemon lists no Agents | Confirm the AI coding tools are on PATH and logged in, then run multica daemon restart. |
| Issues stay queued | Run multica daemon status to confirm the daemon is running and connected to the workspace. |
For more scenarios, see Troubleshooting.
Next steps
- Auth setup — configure email, Google login, and signup scope.
- Environment variables — the full server configuration reference.
- Self-hosting guide — Kubernetes, upgrades, and manual deployment.
- Desktop app — connect Desktop to a self-hosted service.
Complete tutorial
Start from an empty workspace and walk a personal website project through all of Multica's core flows: creating agents, delivering with issues, forming a squad, building skills, and setting up automation.
Workspaces
A workspace is the boundary that keeps teams, work, and agent configuration isolated from each other.