Quickstart (full stack in Docker)
The fastest path to a running server with seeded data and a working API key. The compose stack builds the server fromapps/server/Dockerfile; a one-shot migrations init service (behind the init profile) migrates, provisions the runtime role, and sets its password before the server starts.
- The
migrationsservice and theserverservice both build fromapps/server/Dockerfile(themigrationsbuild target addspsql);serverexposes 3000:3000 with a Node-fetch/healthprobe. pnpm seedruns on the host (not in a container) because it loads the committed eval fixtures; it connects to Postgres on the published port 54320. The demo API key (nameself-host demo) is minted once and is idempotent — re-running prints “already present” rather than re-issuing.APP_USER_PASSWORDdefaults toapp-user-dev(compose) — override it in.envfor anything beyond a throwaway local stack. Because the host-runpnpm seedreads the literalDATABASE_URL(it does not derive the password fromAPP_USER_PASSWORDthe way compose does for theserverservice), change the password in bothAPP_USER_PASSWORDand the password segment ofDATABASE_URL— overriding one alone desyncs the two andpnpm seedfails with an auth error.- A worker container is not in compose yet (no
apps/worker/Dockerfile); BullMQ jobs need a separate deployable before they are part of the self-host stack.
Prerequisites
- Docker (compose v2), Node ≥ 22, pnpm 11.9.0 (
corepack enableuses the version pinned by the repository; on Node ≥ 25, which dropped corepack, runnpm install -g pnpm@11.9.0instead) psqlclient
1. Start the services
2. Configure the environment
Copy.env.example to .env and set the database keys. Then load it into your shell — nothing in the repo auto-loads .env (no dotenv dependency, by design), and the migrate/seed/psql commands below read the variables from the environment:
3. Migrate
4. Provision roles
After migrating — the grants reference tables the migrations create (same order as CI).provision-roles.sql is idempotent and is the only place grants live. Note what it deliberately does NOT grant the runtime role: DELETE anywhere in the memory domain, and anything but INSERT/SELECT on memory_events and audit_log — append-only is enforced at the grant level, below the application.
5. Seed (optional)
dev@localhost, override with SEED_EMAIL). The seed writes through the runtime role inside a set_config('app.user_id', …) transaction — the same RLS gate the app uses — and is idempotent the append-only way: existing rows (matched by content_hash) are skipped, never deleted. Cached fixture vectors are loaded for seeded memories; the seed itself never calls an external API.
Sanity check:
6. Run the test suites against it
Resetting
Deployment boundaries and gaps
- worker container — the server container exists; the worker needs a separate deployable before it is part of the self-host stack.
- hosted dashboard — the dashboard UI is proprietary and maintained in a separate private repository; the Apache server includes the REST/auth routes clients need, but this repository does not ship that UI.
- embedding provider — seeded memories have cached vectors, but every search query and new write still needs an embedding. Set both
LLM_GATEWAY_URLandLLM_GATEWAY_API_KEY; without them, search returns503 embedding_unavailablewhile non-search reads and writes remain available.