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 and 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:
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.yamlDon'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) 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-onlyunless the agent genuinely needs to write. - If it does need to write, prefer
set-permissionto narrow to the specific verb it needs (e.g.updatebut notcreate/delete) rather than granting everything the table's policy happens to allow.
See 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
# 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.pemOr 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_pathif 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/oraudit.shipper(ship to a real backend — Splunk HEC, Datadog, your own service — withheaders/oauth2for 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:
# 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.pathfor SQLite or the database selected bystore.dsnfor PostgreSQL) — roles, identities, API key hashes, connector policy. Losing this without a backup means re-runningrunku auth initand 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.yamlitself, and wherever you're storing the init token backup you made in step 2 (not the.runku-init-tokenfile — 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
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.