Skip to content

Self-hosting

Beta — Venkai runs entirely on your own infrastructure. No component calls out to a Venkai-operated service, and the semantic embedding model runs locally.

Prerequisites

Docker + Compose v2
PostgreSQL Provided by the production compose, or bring your own
Domain + TLS Caddy obtains certificates automatically
Outbound internet Build time only — the embedding model is baked in

Procedure

1. Checkout and configure

git clone <your-venkai-checkout> && cd venkai
cp .env.example .env
git check-ignore -v .env      # must print a .gitignore line before you continue

Fill in the two required secrets:

python -c "import secrets; print('VENKAI_JWT_SECRET=' + secrets.token_urlsafe(64))" >> .env
python -c "import secrets; print('POSTGRES_PASSWORD=' + secrets.token_urlsafe(32))" >> .env

Set VENKAI_ENV=production and VENKAI_ALLOWED_ORIGINS to your dashboard origin. Full table: Environment variables.

2. Build the image before anything else

docker build -t venkai-api .

Do this as a separate, explicit step. A dependency conflict once meant the image had never built successfully while the stack was documented as ready. Prove the artefact exists before trusting a runbook — including this one.

3. Create the external network (dev compose only)

docker network create external-infra

docker-compose.yml declares it external: true, so compose will not create it. The production compose does not need this.

4. Migrate

docker compose -f docker-compose.prod.yml run --rm migrate

The production compose includes a migrate service that runs Alembic once and exits. There is no automatic migration on startup — a stack that silently migrates can silently migrate the wrong way.

5. Start

docker compose -f docker-compose.prod.yml up -d --build
docker compose -f docker-compose.prod.yml ps

6. Verify — from outside the host

# readiness, not just liveness: this opens a DB connection
curl -fsS https://your-domain/api/health
{"status": "ok", "database": "ok"}

{"detail": "database unreachable: …"} with 503 means the API is up and its store is not.

Then prove the round trip actually works, which a health check does not:

# register → key → write → read
curl -sX POST https://your-domain/api/auth/register -H 'Content-Type: application/json' \
  -c c.txt -d '{"email":"you@example.com","password":"a-real-password","organization":"Acme"}'
KEY=$(curl -sX POST https://your-domain/api/auth/api-keys -b c.txt \
  -H 'Content-Type: application/json' -d '{"label":"smoke"}' \
  | python -c 'import json,sys; print(json.load(sys.stdin)["api_key"])')

curl -sX POST https://your-domain/api/memory -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"project_id":"smoke","agent_id":"smoke","content":"self-hosting smoke test","type":"fact"}'

curl -sG https://your-domain/api/context/smoke/relevant -H "Authorization: Bearer $KEY" \
  --data-urlencode 'query=smoke test'

A memory in and the same memory out is the real proof of a working deployment.

Networking

flowchart LR
  I["Internet"] --> C["Caddy :80 :443<br/>TLS termination"]
  C -->|"/api/*"| A["api :8100<br/><b>not host-published</b>"]
  C -->|"/"| D["static dashboard"]
  A --> P[("postgres :5432<br/>internal network only")]

Port 8100 is deliberately not published to the host. Publishing it lets a client bypass Caddy and present its own X-Forwarded-For, which would spoof the rate limiter and the logs. Keep it internal.

For local debugging only, temporarily add ports: ["8100:8100"] — and remove it afterwards.

Bring your own database

VENKAI_DATABASE_URL=postgresql://user:pass@your-host:5432/venkai

Then drop the db service. Requirements: PostgreSQL, a user that can create tables for the migration, and network reachability from the API container. No extensions are required — there is no pgvector dependency (Storage).

Backups

Venkai provides none. This is your database and your policy.

# dump
docker compose -f docker-compose.prod.yml exec -T db \
  pg_dump -U venkai venkai | gzip > venkai-$(date +%F).sql.gz

# restore
gunzip -c venkai-2026-08-18.sql.gz | \
  docker compose -f docker-compose.prod.yml exec -T db psql -U venkai venkai

Also available at the application level: GET /api/export returns an organization's data as JSON.

Test the restore, not the dump

An untested backup is a hypothesis. Restore into a scratch database and run the smoke test above against it.

Upgrades

git pull
docker build -t venkai-api .                                    # 1. prove it builds
docker compose -f docker-compose.prod.yml run --rm migrate      # 2. migrate
docker compose -f docker-compose.prod.yml up -d --build         # 3. roll
curl -fsS https://your-domain/api/health                        # 4. verify

Back up before step 2. Alembic migrations are not automatically reversible.

Rollback

git checkout <previous-tag>
docker build -t venkai-api .
docker compose -f docker-compose.prod.yml up -d --build

If the failed release included a migration, restore the database dump. A forward-only migration cannot be undone by redeploying the old image — the old code will meet the new schema.

What you own

Database backups and restores You
TLS certificates Caddy, automatically
Monitoring and alerting You
Log retention You
Secret rotation You — and rotating VENKAI_JWT_SECRET logs everyone out
Scaling You — the app is stateless; the store is not