docs/guides/self-hosting.md

Self-Hosted Installation

Runtime is MIT. These are the steps for standing up Vocion on your own infrastructure — local dev, a single VPS, or a Kubernetes cluster. Same codebase, same data model, same MCP surface as Vocion Cloud.

If you'd rather we run it for you, see Vocion Cloud.

Install topology

Vocion is layered. You'll touch up to four kinds of repos:

@vocion/core ─────►  framework + dashboard + Postgres schema   (this repo)
@vocion/sdk ──────►  plugin contract                           (npm package)
@vocion/plugin-* ─►  plugins, one per repo                     (npm packages)
vocion-starter ───►  forkable example install                  (separate repo)
<client>-vocion ──►  your production install                   (you own this)

For local dev (this guide), you only need core + whatever plugins you want. Full layering doc: repo-architecture.md.

Requirements

MinimumRecommended
Node.js20.x22.x LTS
Postgres1616 or 17
Dockeroptionalrecommended (Postgres + retrieval stack)
Memory2 GB8 GB+
Disk5 GBdepends on document volume — plan ~1 GB per 100k indexed chunks
OSmacOS, Linux, WSL2Linux (for production)

You'll also need API keys for at least one LLM provider — OpenAI or Anthropic is enough to start.

Quick start (local dev)

# 1. Clone + install
git clone https://github.com/metacto/context-stack.git
cd context-stack
npm install

# 2. Configure env
cp .env.example .env.local
# Edit .env.local — at minimum set:
#   DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/vocion
#   AUTH_SECRET=...   # generate with: openssl rand -base64 32
#   OPENAI_API_KEY=sk-...

# 3. Start Postgres
docker compose up -d postgres

# 4. Apply schema + reference context
npm run db:migrate
npm run workspace:apply

# 5. Run dev server
npm run dev:next
# → http://localhost:3000

That's the whole thing. Sign in with email + password (auth.js is the default — no external auth service), land on the dashboard, poke around.

Environment variables

Full list lives in .env.example. The essential ones:

Required

VarPurpose
DATABASE_URLPostgres connection string
AUTH_SECRETSigning secret for auth.js sessions. Generate with openssl rand -base64 32
OPENAI_API_KEYAt least one LLM provider. See "LLM providers" below for alternatives
VarPurpose
ANTHROPIC_API_KEYEnables skills with provider: anthropic
LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEYLLM observability
VOCION_ORG_IDDefault org for MCP server (otherwise uses the signed-in session)
WORKSPACE_PATHPath to workspace directory. Default workspace/metacto
VOCION_PLUGINSComma-separated plugin specifiers — npm packages or local paths

Optional

VarDefaultPurpose
WORKSPACE_AUTO_COMMITtrueMCP workspace_write_* commits to git
WORKSPACE_AUTO_APPLYtrueMCP workspace_write_* applies to DB after write
VOCION_AUTH_PROVIDERlocalAuth backend. local (default → auth.js, email + password) or clerk (hosted/cloud only)
CLERK_SECRET_KEY / NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYOnly used when VOCION_AUTH_PROVIDER=clerk
STRIPE_SECRET_KEYBilling (skip if not using)
SENTRY_DSNError monitoring
BETTER_STACK_SOURCE_TOKENLog aggregation

Branding (white-label)

The sidebar brand is a white-label slot. All three are NEXT_PUBLIC_*, so they're inlined at build time — set them as Docker --build-args (not runtime env), or in .env.local before next build.

VarDefaultPurpose
NEXT_PUBLIC_BRAND_NAMEVocionWordmark text next to the mark
NEXT_PUBLIC_BRAND_TAGLINEOptional subhead under the wordmark (e.g. agents by Vocion)
NEXT_PUBLIC_BRAND_MARKOptional glyph image src — a path or a data: URI. When set, it replaces the built-in Vocion mark, so your build carries no third-party art.
NEXT_PUBLIC_BRAND_LOCKUPFull lockup image (mark + wordmark as one asset). When set it replaces both the glyph and the wordmark text — only the tagline renders beneath it. Takes precedence over BRAND_MARK/BRAND_NAME.
docker build \
  --build-arg NEXT_PUBLIC_BRAND_NAME="Acme" \
  --build-arg NEXT_PUBLIC_BRAND_TAGLINE="agents by Vocion" \
  --build-arg NEXT_PUBLIC_BRAND_MARK="data:image/svg+xml;base64,$(base64 -w0 acme-mark.svg)" \
  -f packages/core/Dockerfile .

Database setup

Managed Postgres

Any Postgres 16+ works — Neon, Supabase, RDS, Cloud SQL. Set DATABASE_URL and run:

npm run db:migrate

Self-hosted Postgres

The repo ships a docker-compose.yml for local dev:

docker compose up -d postgres

For production, run Postgres separately (Helm chart, Terraform, apt install, whatever fits your ops).

Migrations

Migrations are Drizzle-managed. To apply schema:

npm run db:migrate

To generate a new migration after editing src/models/Schema.ts:

npm run db:generate
npm run db:migrate

Migrations are idempotent; safe to re-run.

Context seeding

Context (agents, skills, object types, workflows) lives in workspace/<org>/ as YAML + markdown. Apply to DB:

npm run workspace:apply

This is idempotent — subsequent runs only touch rows that changed. Every apply records a workspace_version audit row.

To bootstrap a new tenant from existing DB state:

npm run workspace:export -- --org <orgId> --name <dirName>

See workspace/README.md for authoring.

LLM providers

Vocion supports multiple LLM hosts; each plugin skill picks one via the provider field. Set at least one provider's API keyOPENAI_API_KEY or ANTHROPIC_API_KEY — or skill execution will throw on first invocation.

Full provider table + per-skill manifest example: writing-a-plugin.md → Pluggable LLM provider. The platform never silently falls back to a default; missing creds throw with a clear message.

MCP server

The MCP server lets you author skills + workflows and inspect runs from Claude Code, Claude Desktop, Cursor, Zed, or any MCP client.

Claude Code

claude mcp add vocion -- npm --prefix /absolute/path/to/context-stack run mcp:serve

Or add to .mcp.json:

{
  "mcpServers": {
    "vocion": {
      "command": "npm",
      "args": ["--prefix", "/abs/path", "run", "mcp:serve"],
      "env": {
        "VOCION_ORG_ID": "org_...",
        "OPENAI_API_KEY": "sk-..."
      }
    }
  }
}

Other MCP clients

Point any stdio-capable client at tsx src/interfaces/mcp/bin.ts. See MCP reference for the full tool reference.

Plugins

Plugins are npm packages (or local paths) that register additional skills. Install via the VOCION_PLUGINS env var:

export VOCION_PLUGINS=@acme/plugin-a,./local-plugins/my-skill.ts
npm run mcp:serve

Authoring guide: Writing a plugin.

Production deployment

Docker

Build the Next.js image:

FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app ./
ENV NODE_ENV=production
EXPOSE 3000
CMD ["npm", "run", "start"]

Deploy with any container orchestrator (Fly, Railway, Render, ECS, GKE, EKS).

Reverse proxy + TLS

Terminate TLS at nginx / Caddy / your load balancer; forward to the app on port 3000. Set NEXT_PUBLIC_APP_URL to your public URL so auth callbacks resolve correctly.

Scaling considerations

  • Stateless app tier — run as many replicas as you need behind a load balancer. Session is a JWT/cookie issued by auth.js.
  • Postgres is the source of truth — for high-traffic deployments, use a managed service with read replicas.
  • MCP server is single-tenant today (stdio-only). HTTP + OAuth transport lands in Phase 2 for multi-tenant use.
  • Workflow durability — in v1 workflows store state in workflow_run rows. If the process dies mid-run, the run sits paused at its last persisted step. Temporal-backed durability is Phase 7 work.

Backups

Back up Postgres. That's where every skill run, approval decision, context version, and audit record lives. The workspace/<org>/ directory is also important, but it's in git — git is the backup.

Multi-tenant setup

Out of the box, Vocion is multi-tenant — each organization is its own tenant. Rows are scoped by org_id via the oRPC auth guards, and each org has its own workspace/<org>/ directory applied independently.

To onboard a new tenant:

  1. Create the organization
  2. Create workspace/<new-org>/workspace.yaml + empty subdirectories
  3. WORKSPACE_PATH=workspace/<new-org> VOCION_ORG_ID=<org-id> npm run workspace:apply
  4. (Optional) seed objects via scripts or admin UI

Each tenant can have its own plugins enabled via VOCION_PLUGINS — if you're running multi-tenant from one process, list the union. Per-tenant plugin enablement is planned for Phase 3 v0.3.

Observability

  • LLM traces — Langfuse is wired by default. Set LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY + LANGFUSE_HOST. Every skill run creates a trace.
  • Errors — Sentry via SENTRY_DSN.
  • Logs — LogTape writes to stdout by default, Better Stack if configured.
  • Skill + workflow history — in the DB. Visit /dashboard/review for pending items, or query skill_run / workflow_run directly.

Upgrading

git pull
npm install
npm run db:migrate
npm run workspace:apply

Migrations are forward-only and idempotent. Context applies are idempotent. No downtime needed for typical upgrades; new schema changes that would break the running app are released with migration notes.

Troubleshooting

npm run dev aborts with Aborted() from PGLite. The built-in db-server:file target uses PGLite and sometimes gets into a bad state. Switch to Docker Postgres: docker compose up -d postgres && npm run dev:next.

npm run workspace:apply fails with context manifest not found. Your WORKSPACE_PATH is wrong or workspace/<org>/workspace.yaml is missing. The loader walks from the path root.

MCP server reports OPENAI_API_KEY is not set. Env not getting loaded. If running via npm run mcp:serve, the dotenv -c -- prefix should handle it. Direct tsx src/interfaces/mcp/bin.ts requires the env already exported.

Plugin loader errors on first boot. The specifier in VOCION_PLUGINS must resolve as a Node ES module — either an npm package name or an absolute/relative path to a .js, .mjs, or .ts file. The loader logs every failure + still starts with whatever did load.

Workflows get stuck at "paused". Paused workflows are waiting for approval. Visit /dashboard/review or call workflow_run_resume via MCP.

"Database is inconsistent with context." Run npm run workspace:check to see the diff. If something was written to the DB outside workspace-as-code (old seed scripts, manual edits), either export with npm run workspace:export to capture it, or edit the workspace repo to match and re-apply.

What's not self-hostable (today)

Nothing critical — everything in the runtime is MIT and runs on your infra. Some optional integrations require accounts with third parties:

  • auth.js (built-in — no external auth service)
  • Stripe (billing) — optional, only if you're charging end users
  • Langfuse — optional, and can be self-hosted itself

A fully-offline deploy works out of the box — auth.js (email + password) is the default auth backend, so no external auth service is required. Clerk is available as an optional VOCION_AUTH_PROVIDER=clerk path for hosted/cloud builds, but it's not needed to self-host.

Support

  • Docs: /dashboard/docs (in-product) or docs/ in the repo
  • Source: the repo
  • Issues: GitHub issues on the public repo (Phase 8 once OSS launches)
  • Managed ops: MetaCTO Cloud (when a second set of eyes + SLAs make sense)