Setup Guide

Get Rune running
in minutes.

Two paths: deploy with Docker and never touch source code, or clone and run locally for development.

Option A — Production Deploy

Deploy Rune to production without touching source code.

Pull the pre-built Docker image, configure environment variables, and start the engine. Everything — Postgres, migrations, and the API server — runs in containers.

Prerequisites

  • Docker Desktop (or Docker Engine + Compose v2) — docker.com
  • curl — to download the compose file (pre-installed on macOS/Linux)
  • A terminal — bash or zsh
  • Optional: psql if you prefer to connect to Postgres directly — brew install postgresql
1

Download the production Compose file

This file pulls the pre-built engine image from GitHub Container Registry. No source code clone needed.

terminal bash
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.

2

Generate secrets before starting

Run these two commands once. Store the output in a password manager — you'll need them in the next step.

generate secrets bash
# 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
Do not change API_KEY_SALT after generating your first API key. All existing keys are hashed with this salt — changing it invalidates every issued key.
3

Create a .env file

Create .env in the same directory as your compose file and fill in the values from Step 2.

.env env
# 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
The DATABASE_URL uses postgres as the hostname — this is the Docker Compose service name, so containers resolve it automatically over Docker's internal network.
4

Start the engine

Docker Compose will pull the Rune image, start Postgres, run the database migrations, and start the API server — all in one command.

terminal bash
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).

check container status bash
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
5

Create your first tenant and API key

The engine is running but has no tenants yet. Run setup to create your first tenant and receive an API key.

run setup inside the container bash
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:

expected output output
  ✓ Setup complete!

  Tenant ID  84d1aef9-3c2b-4f10-a891-...
  API Key    rune_158Yy...

  ⚠️  Save your API key — it will not be shown again.
🔑 Save the API key immediately. It is shown only once. If lost, generate a new one via the admin dashboard or by re-running setup with a new tenant name.
6

Verify the deployment

Run these checks to confirm the engine is healthy and accepting authenticated requests.

health check (no auth required) bash
curl http://localhost:4078/v1/health
# → {"status":"ok","db":"connected"}
authenticated permission check bash
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..."}
A 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.
7

Install the SDK and make your first check

In your application project, install the SDK and connect to the running engine.

install bash
npm install @runeauth/sdk
index.ts typescript
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
8

Harden for production

Before exposing the engine to the internet, complete these steps.

  • 1 Put a reverse proxy in front. Run Nginx, Caddy, or a cloud load balancer that terminates TLS. Never expose the engine's port (4078) directly to the public internet.
  • 2 Set NODE_ENV=production in your .env. This disables verbose error messages in API responses.
  • 3 Restrict ADMIN_API_KEY access. The /admin dashboard should only be accessible internally (e.g., via VPN or IP allowlist on your proxy).
  • 4 Use a managed Postgres instance (RDS, Cloud SQL, Supabase) instead of the Docker Compose Postgres for production databases. Enable daily backups and point-in-time recovery.
  • 5 Store secrets in a secret manager (AWS Secrets Manager, Vault, Doppler). Never commit .env to source control.
  • 6 Set up log shipping. The engine emits structured JSON logs via Pino. Forward them to Datadog, CloudWatch, Loki, or your log aggregator of choice.
Environment Variables Reference — Production
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).
Troubleshooting — Production Deploy
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.

Run Rune locally from source.

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.

Prerequisites

  • Node.js ≥ 18nodejs.org (we recommend using nvm or Volta to manage versions)
  • pnpm 9.xnpm install -g pnpm@9
  • Docker Desktop (or Docker Engine + Compose v2) — docker.com — used only for local Postgres
  • git — pre-installed on macOS/Linux; git-scm.com for Windows
1

Clone the repository

terminal bash
git clone https://github.com/praveenraj-sk/Rune.git
cd Rune

The repository is a pnpm monorepo with four packages: engine, core, sdk, and cli.

2

Install all workspace dependencies

terminal bash
pnpm install

pnpm resolves and links all packages in the monorepo in a single pass. This typically takes 20–30 seconds on first run.

If you run npm install or yarn by mistake, delete node_modules and run pnpm install again. Mixed package managers can corrupt the workspace symlinks.
3

Start the local Postgres container

The development Compose file starts a Postgres instance on port 5433 (not the default 5432, to avoid conflicts with any locally installed Postgres).

terminal bash
docker compose up -d
# Starts postgres on localhost:5433
verify postgres is running bash
docker compose ps
# Should show: rune-postgres   Up
4

Configure environment variables

Copy the example environment file and review the values. The defaults are pre-configured for local development — no editing required to get started.

terminal bash
cp .env.example .env
.env.example (pre-filled defaults) 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
Never commit .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.
5

Run setup — create the schema and your first API key

The setup script connects to Postgres, creates all tables and indexes, then creates your first tenant and API key.

terminal bash
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.

expected output output
  ✓ 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:

.env — add this line env
RUNE_API_KEY=rune_158Yy...
6

Start the engine with hot reload

terminal bash
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.

expected log output output
{"level":"info","msg":"Rune engine running","port":4078,"env":"development"}
7

Verify the engine is healthy

health check bash
curl http://localhost:4078/v1/health
# → {"status":"ok","db":"connected"}
test a permission check bash
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
8

Run the test suite

Rune has 170 tests across engine, SDK, and core — unit, integration, security attack suite, chaos, and observability.

engine (101 tests) bash
pnpm test
sdk (30 tests) bash
pnpm test:sdk
core (39 tests) bash
cd packages/core && pnpm test
Engine tests require a running Postgres and a valid DATABASE_URL in .env. Make sure Docker Compose is up before running pnpm test.
Daily Development Workflow
every day bash
# 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
Resetting the Database

If your local DB gets into a broken state (e.g. from a failed test run), do a full reset:

full reset — deletes all data including API keys bash
# 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
Environment Variables Reference — Local Dev
Variable Default (local) Description
DATABASE_URL required postgresql://rune:runepassword
@localhost:5433/runedb
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.
Troubleshooting — Local Development
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.