Self-hosting
Engram deploys per client: your Postgres, your object store, your hosting — no memory content ever touches shared infrastructure. The entire stack is configured by environment variables; a new deployment is configuration, not code.
The only external services in any deployment are OpenAI (embeddings and answers — swappable for any OpenAI-compatible endpoint) and Clerk (identity only; it never sees memory content).
Environment variables
| Variable | What it configures |
|---|---|
ENGRAM_MODE | Tenancy model: "self-hosted" (default — one workspace per deployment, first sign-in claims ownership) or "hosted" (multi-org SaaS: each Clerk Organization auto-provisions its own workspace) |
HOSTED_SIGNUPS | Hosted mode only: "open" (default) provisions a workspace for any new organization; "closed" makes Engram Cloud invite-only |
DATABASE_URL | Any Postgres with the vector extension available (Railway, RDS, Supabase, self-hosted) |
S3_BUCKET / AWS_REGION | Object store bucket for memory bodies, and its region |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY | Object store credentials |
S3_ENDPOINT | Optional — point at any S3-compatible store (MinIO, R2, client-VPC); enables path-style addressing |
OPENAI_API_KEY | Embeddings + answers |
COMPAT_BASE_URL / COMPAT_API_KEY | Optional — OpenAI-compatible endpoint (Azure OpenAI, vLLM) via the provider seam |
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY / CLERK_SECRET_KEY | Your own Clerk application (identity only — never memory content) |
PM_WEBHOOK_SECRET | Optional, recommended — HMAC verification for inbound PM webhooks |
<PROVIDER>_CLIENT_ID / _CLIENT_SECRET | Optional per provider — enables one-click OAuth connect for Linear, GitHub, Slack, Atlassian, Notion (paste-credentials works without) |
Deployment recipes
A. Railway (the default)
- New Railway project → add Postgres (pgvector ships in Railway's image).
- Deploy this repo as a service; set the env vars above.
- Bucket: any S3 provider you already have, or Railway's MinIO template with
S3_ENDPOINT. - Run the one-time setup below against the new database.
B. Docker Compose (fully self-contained)
App + pgvector Postgres + MinIO on one host; data stays on that host.
cp .env.self-host.example .env # fill in Clerk + OpenAI keys docker compose up -d --build
The compose stack is written but not yet smoke-tested end-to-end — verify it before handing it to a client.
C. Anywhere Node runs
pnpm install pnpm --filter @engram/web build # Next.js standalone output pnpm --filter @engram/web start
One-time setup (all recipes)
From the repo, with DATABASE_URL (and the S3 vars) pointing at the new stack:
pnpm db:push # schema cd packages/agents pnpm tsx scripts/bootstrap-workspace.ts "Client Name" # empty workspace
The bootstrap creates the pgvector extension, one workspace (it refuses to run if one already exists — one workspace per deploy), starter folders (/company/policies, /departments, /projects), a default employee → viewer@/company role mapping, and the demo Report Drafter agent. Then:
- Sign in at /app — the first user becomes the workspace owner, so sign in before sharing the URL. This first-signer rule applies only when
ENGRAM_MODEis unset orself-hosted. - Mint the owner's personal token:
pnpm tsx scripts/mint-personal-token.ts. - Connect MCP, add connectors, capture the first memories.
For a demo instance with the seeded “Meridian Systems” workspace, run scripts/seed-meridian.ts and scripts/seed-agent.ts instead of the bootstrap. To start a deployment over, scripts/wipe-db.ts --yes drops the whole schema (then re-run db:push and the bootstrap).
What lives where
| Data | Location |
|---|---|
| Memory text, embeddings, metadata, audit log | Your Postgres |
| Memory bodies, uploaded source files | Your bucket |
| Content sent for embedding / answering | OpenAI, or your compatible endpoint |
| User identities (email, SSO) — no memory content | Clerk |