Add a one-command self-hosting setup: `docker compose -f docker-compose.selfhost.yml up -d` starts PostgreSQL, backend (with auto-migration), and frontend. Changes: - docker-compose.selfhost.yml: full stack orchestration (postgres + backend + frontend) - Dockerfile: add entrypoint.sh that auto-runs migrations before server start - Dockerfile.web: multi-stage Next.js build with standalone output - docker/entrypoint.sh: migration + server startup script - .dockerignore: exclude unnecessary files from Docker builds - apps/web/next.config.ts: conditional standalone output for Docker builds - SELF_HOSTING.md: rewrite with Docker Compose as primary approach - README.md: update self-host section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.5 KiB
Self-Hosting Guide
This guide walks you through deploying Multica on your own infrastructure.
Architecture Overview
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.
Quick Start (Docker Compose)
Prerequisites: Docker and Docker Compose.
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 up -d
That's it. This 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.
Rebuilding After Updates
git pull
docker compose -f docker-compose.selfhost.yml up -d --build
Migrations run automatically on each backend startup.
Configuration
All configuration is done via environment variables. Copy .env.example as a starting point.
Required Variables
| 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 |
Email (Required for Authentication)
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) |
Google OAuth (Optional)
| 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) |
File Storage (Optional)
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 |
Server
| 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 |
CLI / Daemon
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 |
Database Setup
Multica requires PostgreSQL 17 with the pgvector extension.
Using Docker Compose (Recommended)
The docker-compose.selfhost.yml includes PostgreSQL. No separate setup needed.
Using Your Own PostgreSQL
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.
Running Migrations Manually
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 up
Manual Setup (Without Docker Compose)
If 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/server
For the frontend:
pnpm install
pnpm build
# Start the frontend (production mode)
cd apps/web
REMOTE_API_URL=http://localhost:8080 pnpm start
Reverse Proxy
In production, put a reverse proxy in front of both the backend and frontend to handle TLS and routing.
Caddy (Recommended)
app.example.com {
reverse_proxy localhost:3000
}
api.example.com {
reverse_proxy localhost:8080
}
Nginx
# 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/ws
Health Check
The backend exposes a health check endpoint:
GET /health
→ {"status":"ok"}
Use this for load balancer health checks or monitoring.
Setting Up the Agent Daemon
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 (
-
Authenticate and start
# Point CLI to your server # # For production deployments with TLS: export MULTICA_APP_URL=https://app.example.com export MULTICA_SERVER_URL=wss://api.example.com/ws # # For local deployments without TLS: # export MULTICA_APP_URL=http://localhost:3000 # export MULTICA_SERVER_URL=ws://localhost:8080/ws # Login (opens browser) multica login # Start the daemon multica daemon startNote: Use
https://andwss://for production deployments behind a TLS-terminating reverse proxy. For local or development deployments without TLS, usehttp://andws://instead.
The daemon auto-detects installed agent CLIs and registers itself with the server. When an agent is assigned a task in Multica, the daemon picks it up, creates an isolated workspace, runs the agent, and reports results back.
Upgrading
git pull
docker compose -f docker-compose.selfhost.yml up -d --build
Migrations run automatically on backend startup. They are idempotent — running them multiple times has no effect.