Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.3ngram.ai/llms.txt

Use this file to discover all available pages before exploring further.

The self-host compose path runs with AUTH_ENABLED=true. It creates one local admin user and enables MCP OAuth/device-flow authentication for clients.

Seeded user

Default credentials:
dev@local.test / DevPassword1!
Override before first boot:
SELFHOST_SEED_EMAIL=you@example.com
SELFHOST_SEED_PASSWORD=replace-this-password
SELFHOST_SEED_NAME=Your Name
The seed service is idempotent. Re-running compose updates the seeded user’s password and profile if the email already exists.

MCP OAuth

Local issuer:
http://localhost:8001
Local MCP endpoint:
http://localhost:8001/mcp
The default redirect allowlist includes local clients, Claude, ChatGPT, and Cursor callback URLs. Override OAUTH_ALLOWED_REDIRECT_URIS when deploying on a custom domain.

Generate strong secrets

JWT_SECRET, MCP_SECRET_KEY, and CRON_SECRET must each be at least 32 random characters. Generate one per secret:
python -c "import secrets; print(secrets.token_hex(32))"
Set them in your .env (copied from .env.selfhost.example) before exposing the stack:
JWT_SECRET=<output of token_hex(32)>
MCP_SECRET_KEY=<a different output>
CRON_SECRET=<a different output>
Use a distinct value for each. Also replace DB_PASSWORD (and the matching DATABASE_URL / DATABASE_MIGRATION_URL) with a real password.

Production-like self-hosting

Before exposing the stack beyond your local machine:
  • Replace JWT_SECRET, MCP_SECRET_KEY, and CRON_SECRET (see above).
  • Set APP_BASE_URL and MCP_ISSUER_URL to HTTPS URLs.
  • Restrict CORS_ALLOWED_ORIGINS to the dashboard origin.
  • Use a real database password and update DATABASE_URL.
  • Put the services behind a TLS-terminating reverse proxy.
The built-in compose secrets are local development defaults. They are publicly visible in this repository. Do not use them for a network-accessible deployment.

Secret enforcement

The config layer fails closed:
  • production / staging: the app refuses to boot if JWT_SECRET, MCP_SECRET_KEY, or CRON_SECRET matches a known weak default (the compose fallbacks or the .env.selfhost.example placeholder), if JWT_SECRET is shorter than 32 characters, or if DATABASE_URL still uses the default engram_dev_password.
  • selfhost (default, loopback): the app boots so docker compose up works from a fresh clone, but logs a loud WARNING for each secret left at a known default. Treat those warnings as a hard blocker before binding to anything beyond loopback.
  • Auth cannot be disabled in any deployed environment (AUTH_ENABLED=true is enforced for production, staging, and selfhost).

Network self-host: fail closed with SELFHOST_NETWORK_MODE

When you expose the self-host stack to a network (anything beyond loopback), set SELFHOST_NETWORK_MODE=true in your .env. This promotes the warnings above to hard boot failures without forcing ENVIRONMENT=production (which would also require DB_REQUIRE_TLS over the internal Docker network). With it on, selfhost refuses to boot if JWT_SECRET, MCP_SECRET_KEY, CRON_SECRET, or the database password is left at a known compose default — the same fail-closed posture as production/staging.
EnvironmentSELFHOST_NETWORK_MODEWeak/default secret behavior
selfhostfalse (default)Loud WARNING, boots (loopback bring-up)
selfhosttrueBoot failure (network-exposed)
production / stagingn/aBoot failure (always)
SELFHOST_NETWORK_MODE=true
Either set SELFHOST_NETWORK_MODE=true, or set ENVIRONMENT=production behind your reverse proxy. Both turn the known-default secrets (and the default DB password) into a boot-time failure rather than a warning.