Skip to main content
Run 3ngram on your own infrastructure. Your data never leaves your servers.

Prerequisites

  • Docker Engine 24+ with Docker Compose v2
  • 2 GB RAM minimum (4 GB recommended)

Quick start

git clone https://github.com/sebastianebg/engram.git
cd engram

# Create environment file
cp .env.selfhost.example .env

# Generate secrets
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
# Copy output into .env for DB_PASSWORD and JWT_SECRET

# Start all services
docker compose -f compose.selfhost.yaml up -d

Services

ServiceURLDescription
Frontendhttp://localhost:3000Web interface
APIhttp://localhost:8000REST API + OpenAPI docs at /docs
MCPhttp://localhost:8001MCP server for AI assistants
DatabaseInternal (port 5432)PostgreSQL 16 + pgvector
Database migrations run automatically on API startup via Alembic.

Configuration

Required

VariableDescription
DB_PASSWORDPostgreSQL password
JWT_SECRETJWT signing secret

Optional: AI features

VariableDescription
OPENAI_API_KEYOpenAI API key for semantic search and embeddings
Without this, 3ngram still works for document storage, fulltext search, and basic memory recall.

Optional: MCP auth

VariableDescription
MCP_AUTH_TOKENBearer token for MCP clients
MCP_ISSUER_URLOAuth issuer URL (default: http://localhost:8001)

Custom ports

VariableDefaultDescription
API_PORT8000API external port
MCP_PORT8001MCP external port
FRONTEND_PORT3000Frontend external port

Database role separation (optional)

For defense-in-depth, split into two roles:
  • Owner role (engram): runs DDL migrations, owns tables
  • App role (engram_app): DML-only runtime, constrained by RLS policies
Set DB_APP_PASSWORD and DATABASE_APP_URL in your .env, then restart.

TLS

For non-localhost database connections:
DATABASE_URL=postgresql://engram:password@db.example.com:5432/engram?sslmode=require
Set DB_REQUIRE_TLS=true to enforce TLS at startup.

Updating

git pull
docker compose -f compose.selfhost.yaml up -d --build
Migrations run automatically on every restart.

Reverse proxy

If running behind nginx or Caddy, update these variables to match your proxy config:
  • NEXT_PUBLIC_API_URL: API URL as seen by browsers
  • CORS_ALLOWED_ORIGINS: your frontend domain
  • MCP_ISSUER_URL: MCP URL as seen by OAuth clients

Troubleshooting

# Check service logs
docker compose -f compose.selfhost.yaml logs db
docker compose -f compose.selfhost.yaml logs api
docker compose -f compose.selfhost.yaml logs mcp

# Health checks
curl http://localhost:8000/health  # API
curl http://localhost:8001/health  # MCP

# Reset everything (data loss!)
docker compose -f compose.selfhost.yaml down -v
docker compose -f compose.selfhost.yaml up -d