This guide walks you through deploying Multica on your own infrastructure.
Multica has three components:
| 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 |
Additionally, each user who wants to run AI agents locally installs the multica CLI and runs the agent daemon on their own machine.
Prerequisites: Docker and Docker Compose.
git clone https://github.com/multica-ai/multica.git
cd multica
cp .env.example .envEdit .env — at minimum, change JWT_SECRET:
JWT_SECRET=$(openssl rand -hex 32)Then start everything:
docker compose -f docker-compose.selfhost.yml up -dThis builds and starts PostgreSQL, the backend (with auto-migration), and the frontend:
- Frontend: http://localhost:3000
- Backend API: http://localhost:8080
The backend automatically runs database migrations on startup — no manual migration step needed.
To run AI agents, you also need to set up the daemon on your local machine. See Setting Up the Agent Daemon below.
git pull
docker compose -f docker-compose.selfhost.yml up -d --buildMigrations run automatically on each backend startup.
All configuration is done via environment variables. Copy .env.example as a starting point.
| Variable | Description | Example |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | postgres://multica:multica@localhost:5432/multica?sslmode=disable |
JWT_SECRET |
Must change from default. Secret key for signing JWT tokens. Use a long random string. | openssl rand -hex 32 |
FRONTEND_ORIGIN |
URL where the frontend is served (used for CORS) | https://app.example.com |
Multica uses email-based magic link authentication via Resend.
| Variable | Description |
|---|---|
RESEND_API_KEY |
Your Resend API key |
RESEND_FROM_EMAIL |
Sender email address (default: noreply@multica.ai) |
| Variable | Description |
|---|---|
GOOGLE_CLIENT_ID |
Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret |
GOOGLE_REDIRECT_URI |
OAuth callback URL (e.g. https://app.example.com/auth/callback) |
For file uploads and attachments, configure S3 and CloudFront:
| Variable | Description |
|---|---|
S3_BUCKET |
S3 bucket name |
S3_REGION |
AWS region (default: us-west-2) |
CLOUDFRONT_DOMAIN |
CloudFront distribution domain |
CLOUDFRONT_KEY_PAIR_ID |
CloudFront key pair ID for signed URLs |
CLOUDFRONT_PRIVATE_KEY |
CloudFront private key (PEM format) |
COOKIE_DOMAIN |
Domain for CloudFront auth cookies |
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
Backend server port |
FRONTEND_PORT |
3000 |
Frontend port |
CORS_ALLOWED_ORIGINS |
Value of FRONTEND_ORIGIN |
Comma-separated list of allowed origins |
LOG_LEVEL |
info |
Log level: debug, info, warn, error |
These are configured on each user's machine, not on the server:
| Variable | Default | Description |
|---|---|---|
MULTICA_SERVER_URL |
ws://localhost:8080/ws |
WebSocket URL for daemon → server connection |
MULTICA_APP_URL |
http://localhost:3000 |
Frontend URL for CLI login flow |
MULTICA_DAEMON_POLL_INTERVAL |
3s |
How often the daemon polls for tasks |
MULTICA_DAEMON_HEARTBEAT_INTERVAL |
15s |
Heartbeat frequency |
Multica requires PostgreSQL 17 with the pgvector extension.
The docker-compose.selfhost.yml includes PostgreSQL. No separate setup needed.
If you prefer to use an existing PostgreSQL instance, ensure the pgvector extension is available:
CREATE EXTENSION IF NOT EXISTS vector;Set DATABASE_URL in your .env and remove the postgres service from the compose file.
The Docker Compose setup runs migrations automatically. If you need to run them manually:
# Using the built binary
./server/bin/migrate up
# Or from source
cd server && go run ./cmd/migrate upIf you prefer to build and run services manually:
Prerequisites: Go 1.26+, Node.js 20+, pnpm 10.28+, PostgreSQL 17 with pgvector.
# Start your PostgreSQL (or use: docker compose up -d postgres)
# Build the backend
make build
# Run database migrations
DATABASE_URL="your-database-url" ./server/bin/migrate up
# Start the backend server
DATABASE_URL="your-database-url" PORT=8080 JWT_SECRET="your-secret" ./server/bin/serverFor the frontend:
pnpm install
pnpm build
# Start the frontend (production mode)
cd apps/web
REMOTE_API_URL=http://localhost:8080 pnpm startIn production, put a reverse proxy in front of both the backend and frontend to handle TLS and routing.
app.example.com {
reverse_proxy localhost:3000
}
api.example.com {
reverse_proxy localhost:8080
}
# Frontend
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Backend API
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# WebSocket support
location /ws {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400;
}
}When using separate domains for frontend and backend, set these environment variables accordingly:
# Backend
FRONTEND_ORIGIN=https://app.example.com
CORS_ALLOWED_ORIGINS=https://app.example.com
# Frontend (set before building the frontend image)
REMOTE_API_URL=https://api.example.com
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_WS_URL=wss://api.example.com/wsThe backend exposes a health check endpoint:
GET /health
→ {"status":"ok"}
Use this for load balancer health checks or monitoring.
The daemon runs on your local machine (not inside Docker). It detects installed AI agent CLIs, registers them as runtimes with the server, and executes tasks when agents are assigned work.
Each team member who wants to run AI agents locally needs to:
-
Install the CLI
brew tap multica-ai/tap brew install multica-cli
-
Install an AI agent CLI — at least one of:
- Claude Code (
claudeon PATH) - Codex (
codexon PATH)
- Claude Code (
-
Point the CLI to your server
The CLI defaults to the hosted Multica service. For self-hosted setups, you must set the server URLs before logging in:
# Local Docker Compose deployment (default ports): export MULTICA_APP_URL=http://localhost:3000 export MULTICA_SERVER_URL=ws://localhost:8080/ws # Production deployment with TLS: # export MULTICA_APP_URL=https://app.example.com # export MULTICA_SERVER_URL=wss://api.example.com/ws
Note: Use
http://andws://for local deployments without TLS. Usehttps://andwss://for production deployments behind a TLS-terminating reverse proxy.You can also set these persistently so you don't need to export them each time:
multica config set app_url http://localhost:3000 multica config set server_url ws://localhost:8080/ws
-
Authenticate and start
# Login (opens browser to your frontend) multica login # Start the daemon multica daemon start
The login flow opens your browser, authenticates you via the frontend, and stores a personal access token locally. The daemon then uses this token to register with the backend.
To verify the daemon is running:
multica daemon status
git pull
docker compose -f docker-compose.selfhost.yml up -d --buildMigrations run automatically on backend startup. They are idempotent — running them multiple times has no effect.