Self-Hosting
Vinculum is AGPL v3. This guide gets you from zero to a running self-hosted instance — copy-pasteable and end-to-end.
Self-hosted installs receive the product app (dashboard, MCP server, runner) only — the marketing and pricing pages at vinculum.run are not included and are not needed.
Prerequisites
Docker path (recommended for teams)
- Docker ≥ 24 and Docker Compose v2
- A domain name with TLS termination (Caddy or Traefik work well)
- 1 GB RAM minimum, 2 GB recommended
- A GitHub OAuth app — create one at
github.com/settings/developers
Bare-metal path
- Ubuntu 22.04 or 24.04 (or equivalent Linux with systemd)
- Python 3.12+ and
uv - Node.js 22 and pnpm
- PostgreSQL 17 with the pgvector extension
Quick path — Coolify
If you use Coolify for self-hosted app management:
- Add resource → Docker Compose → from GitHub
- Repository:
whalefall-media/vinculum - Set the required env vars (see below)
- Deploy — Coolify runs
docker compose up -d
The compose file includes a PostgreSQL 17 service with pgvector pre-loaded. Migrations run automatically on first boot.
Manual path — Docker Compose
git clone https://github.com/whalefall-media/vinculum.git
cd vinculum
cp env.example .envEdit .env — at minimum:
VINCULUM_DATABASE_URL=postgresql://vinculum_app:changeme@db:5432/vinculum
VINCULUM_BASE_URL=https://your-domain.example.com
VINCULUM_GITHUB_CLIENT_ID=<your-github-oauth-app-client-id>
VINCULUM_GITHUB_CLIENT_SECRET=<your-github-oauth-app-client-secret>
VINCULUM_JWT_SECRET=$(openssl rand -hex 32)Then start:
docker compose up -dThe server starts on port 31415. Point your reverse proxy at it.
Caddy reverse proxy
your-domain.example.com {
reverse_proxy localhost:31415
}nginx reverse proxy
Disable proxy buffering for SSE
The dashboard SSE stream (/api/dashboard/stream) requires buffering to be disabled. Without this, real-time updates won't reach the browser.
server {
listen 443 ssl;
server_name your-domain.example.com;
location / {
proxy_pass http://127.0.0.1:31415;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_buffering off;
}
}Manual path — bare metal
Always install editable
Use uv pip install -e . (with -e). Without the editable flag, git pullupdates won't reach the running server until you reinstall — a silent divergence that has caused outages.
git clone https://github.com/whalefall-media/vinculum.git
cd vinculum
uv pip install -e .
cp env.example .env
# edit .env
vinculum migrate
vinculum runVerify the install is editable:
cat ~/.local/lib/python*/site-packages/vinculum_run-*.dist-info/direct_url.json
# Should show: "dir_info": {"editable": true}Spawn runner
Spawning is runner-only. spawn_grunt enqueues a spawn_request row that a paired vinculum-runner — a small Go binary on the machine where grunts should execute — claims and runs. The runner long-polls the server over outbound HTTPS (/runner/poll); there is no in-process Popen path, no host-shim, and no daemon to install.
Pair a runner by generating a token (generate_pairing_token MCP tool, or Settings → Runners → Connect a runner in the dashboard) and running the install one-liner it emits:
# Linux / macOS native install (registers a start-at-login unit)
curl -fsSL https://vinculum.run/install.sh | sh
# Or via Docker (any OS)
docker run -d --restart unless-stopped --name vinculum-runner \
-e VINCULUM_PAIRING_TOKEN=<your-token> \
ghcr.io/whalefall-media/vinculum-runner:latest| Platform | Supervisor | Artifact |
|---|---|---|
| Linux + systemd + linger | systemd user unit (auto-restart) | runner's own start-at-login unit |
| macOS | launchd LaunchAgent | ~/.config/vinculum/runner/ + launchd plist |
| Windows | Scheduled Task | %LOCALAPPDATA%\\Vinculum\\ |
| Any OS (Docker) | docker run --restart unless-stopped | container |
Server-side reconciliation
spawn_log.status reconciliation runs server-side as the spawn_reconcile watchdog tick — there is no host-side reconciler script or timer.
Environment variables
Required
| Variable | Description |
|---|---|
VINCULUM_DATABASE_URL | PostgreSQL connection string. Role must have CREATE on the vinculum schema. |
VINCULUM_BASE_URL | Public URL of your install, e.g. https://vinculum.example.com. |
VINCULUM_GITHUB_CLIENT_ID | GitHub OAuth app client ID. Callback: <BASE_URL>/api/auth/github/callback. |
VINCULUM_GITHUB_CLIENT_SECRET | GitHub OAuth app client secret. |
VINCULUM_JWT_SECRET | Random secret for signing session JWTs. Generate: openssl rand -hex 32. Never reuse across installs. |
AI intelligence (optional, but recommended)
| Variable | Description |
|---|---|
VINCULUM_ANTHROPIC_API_KEY | Anthropic key for Haiku. Enables per-entry delta classification and thread auto-titling. |
VINCULUM_VOYAGE_API_KEY | Voyage AI key for embedding generation (semantic search via pgvector). |
Server tuning
| Variable | Default | Description |
|---|---|---|
VINCULUM_HOST | 0.0.0.0 | Listen address. |
VINCULUM_PORT | 31415 | Listen port. |
VINCULUM_TRANSPORT | streamable-http | MCP transport. |
VINCULUM_WEB_BASE_URL | Same as VINCULUM_BASE_URL | Frontend URL if different origin. |
Auth extras
| Variable | Description |
|---|---|
VINCULUM_FORGEJO_CLIENT_ID / _SECRET | Forgejo OAuth (self-hosted Gitea). |
VINCULUM_INGEST_SECRET | Bearer token for /api/ingest/conversation. |
VINCULUM_AUTH_TOKEN_* | Capability-scoped bearer tokens for MCP tool access. |
Billing (hosted tiers only)
Leave unset to disable Stripe entirely. Self-hosted installs don't need these.
| Variable | Description |
|---|---|
STRIPE_SECRET_KEY | Stripe secret key. |
STRIPE_WEBHOOK_SECRET | Signing secret from the Stripe webhook dashboard. |
STRIPE_PRICE_ID_PRO | Stripe price ID for the Pro tier. |
STRIPE_PRICE_ID_TEAM | Stripe price ID for the Team tier. |
Infrastructure tools
All default to false. Enable to unlock MCP tools for inspecting your infrastructure from a Claude session.
| Variable | Unlocks |
|---|---|
VINCULUM_INFRA_DATABASE_ENABLED=true | query_db, list_tables tools |
VINCULUM_INFRA_DOCKER_ENABLED=true | docker_status, docker_logs tools |
VINCULUM_INFRA_SYSTEM_ENABLED=true | system_stats tool |
VINCULUM_INFRA_TYPESENSE_ENABLED=true | Full-text search via Typesense |
Verifying the install
Server is up
curl http://localhost:31415/healthz
# → {"ok": true, "service": "vinculum"}Dashboard loads
Open https://your-domain.example.com/login (or http://localhost:31415/login for bare-metal) and sign in — you land on your own dashboard at /d/<your-username>, showing the empty-project state with six branch cards.
Sign in works
Click Sign In → GitHub. You should be redirected to GitHub and back, then land on the dashboard as a signed-in user.
MCP endpoint responds
curl -X POST https://your-domain.example.com/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0"}}}'
# → {"jsonrpc":"2.0","result":{"protocolVersion":"2024-11-05",...}}Connect Claude Code and spawn a grunt
cd ~/your-project
uvx vinculum-run config --client=claude-code --write -yThen in Claude Code, call spawn_gruntwith a test directive. You're fully operational when a grunt session appears in the Sessions panel.
After deployment, the dashboard MetricStrip shows live health — branch counts, active sessions, and entry velocity at a glance.
Cross-platform notes
The vinculum-runner ships native installers for Linux, macOS, and Windows, plus a Docker image that runs anywhere. There is no platform-specific daemon — the runner is the single spawn path on every OS.
Backup and restore
# Backup
pg_dump -Fc "$VINCULUM_DATABASE_URL" > vinculum_$(date +%Y%m%d).dump
# Restore to a fresh database
createdb vinculum
pg_restore -d "$VINCULUM_DATABASE_URL" vinculum_20260101.dumpAutomated backups
An R2 automated backup pipeline is specced for post-launch (#125). Until then, set up a cron job or Coolify backup policy for the Postgres volume.
Upgrading
Docker Compose
cd vinculum
git pull
docker compose pull
docker compose up -dMigrations run automatically on startup.
Bare metal
cd vinculum
git pull
uv pip install -e .
vinculum migrate
systemctl --user restart vinculum-mcp