# Running Runku in production

This is the checklist for taking Runku past "trying it out" into
something pointed at a real database that real agents will use. Read
[installation.md](installation.md) and
[auth-and-permissions.md](auth-and-permissions.md) first if you haven't
already — this page assumes you have.

## 1. Initialize a clean production store

Create a new admin store with `runku auth init`. Save the initial credential in a secrets manager, remove the temporary token file as instructed, and create narrower identities for normal use. Never import credentials from examples or another environment.

## 2. Generate real credentials, and treat the init token file with care

`runku auth init`'s interactive path writes one initial API key to a
plaintext file (`.runku-init-token`, next to your `config.yaml`) and
prints it once. `runku serve` refuses to start while that file exists —
this is deliberate friction, not a bug to route around:

```sh
runku auth init --config config.yaml
# copy the printed token into a real secrets manager / password manager
rm .runku-init-token
runku serve --config config.yaml
```

Don't script around the refusal (e.g. by auto-deleting the file
immediately after `init` without actually saving the token anywhere) —
the whole point is a human deliberately handling the one credential that
can never be recovered after this step. Once you have a working initial
identity, create narrower, purpose-specific identities and roles for
real agents (see [auth-and-permissions.md](auth-and-permissions.md)) and
consider disabling or rotating the initial one.

## 3. Prefer OIDC over long-lived API keys where you can

If you already run an identity provider (Keycloak, Okta, Auth0, Entra
ID — anything standards-compliant), configure `auth.oidc` and skip
API keys for human-facing or rotating access entirely. `runku auth init`
detects `auth.oidc` being set and skips API-key generation for exactly
this reason. API keys remain the right choice for long-running service
agents that can't do an interactive login — just scope each one to a
narrow role and rotate it periodically (`runku auth apikey create` /
`revoke`).

## 4. Design least-privilege roles before pointing agents at real data

Don't bind every identity to an unrestricted role by default. For each
agent or team that will call Runku:

- Grant only the tables/procedures it actually needs
  (`runku auth role grant-table`/`grant-procedure`).
- Default to `--read-only` unless the agent genuinely needs to write.
- If it does need to write, prefer `set-permission` to narrow to the
  specific verb it needs (e.g. `update` but not `create`/`delete`)
  rather than granting everything the table's policy happens to allow.

See [policy.md](policy.md) for masking and mutation column allow-lists —
those apply to every caller regardless of role, and are your first line
of defense; per-identity roles are the second.

## 5. Turn on TLS, or terminate it in front of Runku

```yaml
# Terminate TLS directly in Runku. Omit this block behind a trusted TLS proxy.
server:
  tls:
    # Require HTTPS on the Runku listener.
    enabled: true
    # Mounted PEM certificate chain and its matching private key.
    cert_file: /etc/runku/tls/cert.pem
    key_file: /etc/runku/tls/key.pem
```

Or leave it plain HTTP behind a reverse proxy/load balancer that
already terminates TLS — either is fine, but don't run bearer tokens or
API keys over plaintext HTTP on an untrusted network.

## 6. Decide on audit durability and shipping deliberately

The stdout audit line is always on and can't be turned off — capture it
with whatever your platform already uses for container/process logs.
Beyond that:

- Set `audit.sqlite_path` if you want `/reports/*` and the portal to
  work at all (they read from this buffer).
- If you want a durable trail beyond your container's log retention,
  configure `audit.log_file` (rotated on-disk) and/or `audit.shipper`
  (ship to a real backend — Splunk HEC, Datadog, your own service — with
  `headers`/`oauth2` for authentication, never unauthenticated over an
  untrusted network).
- Remember the outbox guarantee: shipped rows are only purged after a
  confirmed successful delivery — a misconfigured or unreachable
  shipper endpoint doesn't lose data, it just accumulates in the local
  buffer, so monitor its size if you enable shipping.

## 7. Use a real store backend appropriate to your deployment shape

PostgreSQL is the recommended production backend for the admin store. It also
supports high availability and multiple `runku serve` instances sharing one
admin database:

```yaml
# Production admin store for policies, roles, identities and API-key hashes.
store:
  # PostgreSQL supports backups, HA and multiple Runku instances.
  driver: postgres
  # Dedicated admin-database DSN supplied through the environment.
  dsn: ${RUNKU_MCP_STORE_DSN}
```

Keep the DSN in the environment or your secrets manager, require TLS when the
database is remote, and back up the PostgreSQL database using your normal
database backup process.

SQLite remains supported for small, single-instance installations where its
simpler operational model is preferable.

## 8. Back up the right files

- **The admin store** (`store.path` for SQLite or the database selected by
  `store.dsn` for PostgreSQL) — roles,
  identities, API key hashes, connector policy. Losing this without a
  backup means re-running `runku auth init` and rebuilding every role/
  policy decision from scratch.
- **The audit SQLite buffer** (`audit.sqlite_path`), if you rely on it
  for anything beyond a rolling local window and don't ship it
  elsewhere.
- **`config.yaml`** itself, and wherever you're storing the init token
  backup you made in step 2 (not the `.runku-init-token` file — that
  should already be deleted; the copy you made in your actual secrets
  manager).

None of these are the target database Runku protects — back that up the
way you already do, independently.

## 9. Apply policy changes without downtime

```sh
runku policy apply --file policy.yaml <connector-id>
kill -HUP <pid>
```

`SIGHUP` reloads policy and role/identity bindings from the admin store
in place — no restart, no dropped connections mid-request. Build this
into your deployment tooling rather than restarting the process for
every policy change.

## 10. Verify before you trust it

Before pointing real agents at a real deployment: confirm masked columns
genuinely don't appear anywhere (schema, results, `schema_overview`,
audit log — try filtering by one, it should be rejected); confirm a
restricted identity is actually restricted (attempt something it
shouldn't be able to do, confirm it's blocked and logged); confirm
`runku serve` refuses to start with a stale `.runku-init-token` file
present. Perform these checks after every upgrade and configuration change.
