Two paths: deploy with Docker and never touch source code, or clone and run locally for development.
Pull the pre-built Docker image, configure environment variables, and start the engine. Everything — Postgres, migrations, and the API server — runs in containers.
brew install postgresqlThis file pulls the pre-built engine image from GitHub Container Registry. No source code clone needed.
curl -O https://raw.githubusercontent.com/praveenraj-sk/Rune/main/docker-compose.prod.yml
This downloads a single docker-compose.prod.yml file to your current directory.
Inspect it before running — it defines two services: postgres and rune-engine.
Run these two commands once. Store the output in a password manager — you'll need them in the next step.
# Salt for hashing API keys (required — never change after issuing keys) openssl rand -hex 32 # → e.g. 3f9a2b... ← copy this as API_KEY_SALT # Admin dashboard key (optional — omit to disable the /dashboard UI) openssl rand -hex 32 # → e.g. 8d71cc... ← copy this as ADMIN_API_KEY
API_KEY_SALT after generating your first API key.
All existing keys are hashed with this salt — changing it invalidates every issued key.
.env fileCreate .env in the same directory as your compose file and fill in the values from Step 2.
# Required DATABASE_URL=postgresql://rune:runepassword@postgres:5432/runedb PORT=4078 NODE_ENV=production # Security — paste values from Step 2 API_KEY_SALT=your-32-byte-hex-from-openssl ADMIN_API_KEY=your-32-byte-hex-from-openssl # Tuning (defaults are production-safe) MAX_CACHE_SIZE=10000 MAX_BFS_DEPTH=20 MAX_BFS_NODES=1000 RATE_LIMIT_MAX=100 RATE_LIMIT_WINDOW_MS=10000
DATABASE_URL uses postgres as the hostname — this is the Docker Compose service name,
so containers resolve it automatically over Docker's internal network.
Docker Compose will pull the Rune image, start Postgres, run the database migrations, and start the API server — all in one command.
docker compose -f docker-compose.prod.yml up -d
The first run downloads the image (~80MB). Subsequent starts are instant.
Both services run in the background (-d flag).
docker compose -f docker-compose.prod.yml ps # Both services should show "Up" or "running" docker compose -f docker-compose.prod.yml logs rune-engine # Should show: Rune engine running on port 4078
The engine is running but has no tenants yet. Run setup to create your first tenant and receive an API key.
docker compose -f docker-compose.prod.yml exec rune-engine pnpm run setup
When prompted, enter a name for your tenant (e.g. acme-corp). Setup will output:
✓ Setup complete! Tenant ID 84d1aef9-3c2b-4f10-a891-... API Key rune_158Yy... ⚠️ Save your API key — it will not be shown again.
Run these checks to confirm the engine is healthy and accepting authenticated requests.
curl http://localhost:4078/v1/health
# → {"status":"ok","db":"connected"}
curl -s -X POST http://localhost:4078/v1/can \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"subject":"user:test","action":"read","object":"doc:hello"}' | jq . # Expected (no tuples yet — this is correct behaviour): # {"decision":"deny","status":"NOT_FOUND","reason":"No path found..."}
NOT_FOUND deny is the expected response on a fresh install — it means the engine is running correctly and authenticating your key. NOT_FOUND simply means no relationship tuples have been written yet.
In your application project, install the SDK and connect to the running engine.
npm install @runeauth/sdk
import { Rune } from '@runeauth/sdk' const rune = new Rune({ apiKey: process.env.RUNE_API_KEY, // API key from Step 5 baseUrl: 'http://localhost:4078', }) // Grant alice viewer access on doc:report await rune.allow({ subject: 'user:alice', relation: 'viewer', object: 'doc:report', }) // Check access — returns ALLOW const result = await rune.can('user:alice').do('read').on('doc:report').check() console.log(result.status) // "ALLOW" console.log(result.trace) // full decision path
Before exposing the engine to the internet, complete these steps.
NODE_ENV=production in your .env. This disables verbose error messages in API responses.
ADMIN_API_KEY access. The /admin dashboard should only be accessible internally (e.g., via VPN or IP allowlist on your proxy).
.env to source control.
| Variable | Default | Description |
|---|---|---|
DATABASE_URL required |
— | Postgres connection string. Use the internal hostname in Docker/Render environments. |
PORT |
4078 |
HTTP port the engine listens on. |
NODE_ENV |
development |
Set to production to suppress verbose error details in API responses. |
API_KEY_SALT required |
— | 32-byte hex salt for SHA-256 API key hashing. Generate once with openssl rand -hex 32. Never change after issuing keys. |
ADMIN_API_KEY |
— | If set, enables the /admin dashboard UI. If omitted, the dashboard is fully disabled. |
MAX_CACHE_SIZE |
10000 |
Maximum LRU cache entries per tenant. Increase for high-traffic deployments with many distinct subjects/objects. |
MAX_BFS_DEPTH |
20 |
Maximum graph traversal depth per check. Guards against deeply nested relationship chains. |
MAX_BFS_NODES |
1000 |
Maximum nodes visited per check. Guards against wide graph traversals (large groups). |
RATE_LIMIT_MAX |
100 |
Maximum requests per API key per rate-limit window. |
RATE_LIMIT_WINDOW_MS |
10000 |
Rate-limit window duration in milliseconds (default: 10 seconds). |
| Symptom | Fix |
|---|---|
| Container exits immediately | Run docker compose logs rune-engine. Most likely DATABASE_URL is missing or Postgres hasn't finished starting — add a depends_on: condition: service_healthy or wait 5 seconds and retry. |
/v1/health returns db: "error" |
Postgres is unreachable. Verify the hostname in DATABASE_URL matches the Compose service name (postgres). Check docker compose ps — both services must be running. |
API returns 401 Unauthorized |
The x-api-key header is missing, wrong, or the key was generated against a different API_KEY_SALT. Re-run setup if you changed the salt after creating keys. |
| Admin dashboard returns 403 | You must pass ADMIN_API_KEY in the x-admin-key header (not the regular API key). If ADMIN_API_KEY was not set in .env, the dashboard is disabled entirely. |
| Port 4078 already in use | Either change PORT in .env, or free the port: lsof -ti:4078 | xargs kill -9 |
Decisions are always NOT_FOUND |
Expected on a fresh install — no relationship tuples exist yet. Write tuples with rune.allow() or via POST /v1/tuples, then re-check. |
Clone the monorepo, start a local Postgres container, and run the engine with hot reload. This path is for contributors, or developers who want to customise the engine.
npm install -g pnpm@9git clone https://github.com/praveenraj-sk/Rune.git cd Rune
The repository is a pnpm monorepo with four packages: engine, core, sdk, and cli.
pnpm install
pnpm resolves and links all packages in the monorepo in a single pass. This typically takes 20–30 seconds on first run.
npm install or yarn by mistake, delete node_modules and run pnpm install again. Mixed package managers can corrupt the workspace symlinks.
The development Compose file starts a Postgres instance on port 5433 (not the default 5432, to avoid conflicts with any locally installed Postgres).
docker compose up -d
# Starts postgres on localhost:5433
docker compose ps
# Should show: rune-postgres Up
Copy the example environment file and review the values. The defaults are pre-configured for local development — no editing required to get started.
cp .env.example .env
DATABASE_URL=postgresql://rune:runepassword@localhost:5433/runedb PORT=4078 NODE_ENV=development API_KEY_SALT=dev-salt-change-in-production CACHE_MAX_SIZE=10000 BFS_MAX_DEPTH=20 BFS_MAX_NODES=1000
.env — it is in .gitignore.
The default API_KEY_SALT is fine for local development, but must be replaced with a strong random value in any shared or production environment.
The setup script connects to Postgres, creates all tables and indexes, then creates your first tenant and API key.
pnpm run setup # Note: use "pnpm run setup", NOT "pnpm setup" # "pnpm setup" installs pnpm itself — not what you want
When prompted, enter any tenant name (e.g. dev). Save the printed API key.
✓ Setup complete! Tenant ID 84d1aef9-... API Key rune_158Yy... ⚠️ Save your API key — it will not be shown again.
Add the API key to your .env so tests and the CLI can use it:
RUNE_API_KEY=rune_158Yy...
pnpm dev
The engine starts at http://localhost:4078 using tsx watch for hot reload.
Any change to a file in packages/engine/src/ triggers an automatic restart in ~500ms.
{"level":"info","msg":"Rune engine running","port":4078,"env":"development"}
curl http://localhost:4078/v1/health
# → {"status":"ok","db":"connected"}
curl -s -X POST http://localhost:4078/v1/can \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"subject":"user:alice","action":"read","object":"doc:report"}' | jq . # → {"decision":"deny","status":"NOT_FOUND",...} ← correct, no tuples yet
Rune has 170 tests across engine, SDK, and core — unit, integration, security attack suite, chaos, and observability.
pnpm test
pnpm test:sdk
cd packages/core && pnpm test
DATABASE_URL in .env. Make sure Docker Compose is up before running pnpm test.
# 1. Make sure Postgres is running docker compose up -d # 2. Start the engine with hot reload (in a dedicated terminal) pnpm dev # 3. Edit files in packages/engine/src/ — engine reloads automatically # 4. Run tests after changes pnpm test # 5. Typecheck all packages pnpm run typecheck
If your local DB gets into a broken state (e.g. from a failed test run), do a full reset:
# 1. Remove the Postgres volume docker compose down -v # 2. Start a fresh Postgres docker compose up -d # 3. Re-create schema + new API key pnpm run setup # 4. Update RUNE_API_KEY in .env with the new key
| Variable | Default (local) | Description |
|---|---|---|
DATABASE_URL required |
postgresql://rune:runepassword |
Points to the local Docker Postgres on port 5433. |
PORT |
4078 |
Engine HTTP port. |
NODE_ENV |
development |
Enables verbose error output in API responses (never use in production). |
API_KEY_SALT |
dev-salt-change-in-production |
Salt for hashing API keys. Fine for local dev — must be a strong random value in production. |
CACHE_MAX_SIZE |
10000 |
LRU cache entries. Lower for dev machines with limited RAM. |
BFS_MAX_DEPTH |
20 |
Max graph traversal depth. Keep at 20 to match production behaviour in tests. |
BFS_MAX_NODES |
1000 |
Max nodes visited per check. Keep at 1000 to match production. |
| Symptom | Fix |
|---|---|
| Invalid Rune config on startup | DATABASE_URL is missing. Make sure you ran cp .env.example .env and the .env file is at the project root. |
role "rune" does not exist |
Postgres container is not running or is on the wrong port. Run docker compose up -d and verify with docker compose ps. |
EADDRINUSE :4078 |
Another process is on port 4078. Free it: lsof -ti:4078 | xargs kill -9 |
| Tests fail with 401 | DB is in a dirty state from a previous failed run. Full reset: docker compose down -v && docker compose up -d && pnpm run setup, then update RUNE_API_KEY in .env. |
pnpm setup installs pnpm itself |
Use pnpm run setup (with run) — this calls the project's setup script, not pnpm's built-in setup command. |
| TypeScript errors after pulling changes | Run pnpm install first — new dependencies may have been added. Then run pnpm run typecheck. |
| SDK tests fail with network errors | SDK tests expect the engine to be running at http://localhost:4078. Start it with pnpm dev in a separate terminal first. |