Helm values reference
Every value of the Helm chart runr 0.5.8 from BRF's public Helm repository, grouped by top-level key, with its default from the chart's values.yaml and what the templates do with it. helm show values brf-tech/runr --version 0.5.8 prints the file with its comments. How these values fit together in a production install is described in Installing RunR in production.
- Validation
- Console and gateway:
domain, cors, gateway, ingress, clientIp
- Application pods:
api, ui, reverb, horizon, scheduler, app, queue, cache, extraEnv
- Data services:
database, redis, storage, mail, mailpit
- Secret and first administrator:
secretName, secret, admin, bootstrap
- Edition:
edition, tenancy, authProvider, ticket
- Platform settings:
quota, audit, auth, billing, branding, signup
- Regions and features:
knative, observability, sandboxApi, gitDeploy, supplyChain, images, ai, bindings
- Scheduling and cluster:
podColocation, placement, networkPolicy, imagePullSecrets, imagePullSecretsFromDefaultServiceAccount, initImage, namespace, createNamespace, projectNamespace, deployTimestamp
Most values become environment variables of the API, Horizon, scheduler and Reverb pods and of the hook Jobs through the ConfigMap runr-config; the variable is named where it helps.
Validation#
Helm checks the values against the chart's values.schema.json on install, upgrade, template and lint. The schema covers these keys:
| Keys |
Checked |
domain |
Must be present, as a string (the default "" passes) |
edition |
single-tenant or multi-tenant |
admin.username, admin.email, admin.password |
A lower-case letter followed by 2–31 of a-z, 0-9, _, ., -; an e-mail address; a string |
ingress.enabled, ingress.className, ingress.tls.* |
Types; ingress.tls.mode is "", letsencrypt, custom, secret or none |
cors.allowedOrigins |
A list of origins (https://host[:port]) or one comma-separated string of them |
gateway.affinity, api.affinity, ui.affinity |
Objects |
api.storage.size, database.postgres.storage |
<number>Gi |
api.storage.accessModes, api.storage.storageClassName, database.postgres.storageClassName |
A non-empty list of ReadWriteOnce / ReadWriteMany; strings |
database.password, database.existingSecret, database.existingSecretPasswordKey, and the same keys and username of redis and mail |
Strings |
mail.encryption |
"", starttls or tls |
storage.application, storage.database |
<number>Gi or empty |
quota.cpu, quota.memoryGiB |
A number of at least 0, a numeric string or empty |
clientIp.trustedProxies, clientIp.proxyHops |
A string; an integer of at least 0 |
ai.* |
huggingface.endpoint an http(s) URL; gpu.defaultTimeout 1–3600; gpu.progressDeadline seconds or minutes (1800s); gpu.scaleToZeroRetention seconds, minutes or hours (5m); modelStorage.sizeGi at least 1; modelStorage.accessMode ReadWriteMany or ReadWriteOnce; the image and chart version keys are strings |
billing.* |
enabled a boolean; currency three capital letters (ISO 4217); webhookUrl an http(s) URL or empty |
branding.* |
productName up to 80 and companyName up to 120 characters; logos and favicon empty, an https URL, a path, a data:image/…;base64,… URI (up to 750000 characters) or a ConfigMap / Secret key reference; colours #rgb / #rrggbb (primary also as the 300–900 scale); supportUrl an http(s) URL, supportEmail an address; up to 8 footerLinks with a label and an http(s) or mailto: URL |
observability.* |
tracing.defaultMode managed, reuse, external or off; tracing.knativeSampleRate 0–1; tracing.tempoStorage <number>Mi, Gi or Ti; lower bounds of the retention, interval and count keys (uptime.minIntervalSeconds at least 10); replay.ingestUrl an http(s) URL or empty |
sandboxApi.enabled, sandboxApi.namespacePrefix, sandboxApi.allowCustomImages |
Booleans; namespacePrefix a DNS label of up to 22 characters |
bindings.namespace, bindings.retentionDays, bindings.storageClass, bindings.quotas.* |
A DNS label; at least 1; a string; integers of at least 0 |
gitDeploy.* |
maxPushMb 1–60; remoteOwner letters, digits, ., _, -; webhookBaseUrl an http(s) URL or empty; previews.limit 0–20, previews.ttlHours 1–720, previews.maxLimit 0–100 |
supplyChain.* |
enabled a boolean (or "true" / "false"); the image, namespace, source and repository keys strings; the hour keys and policyController.chartVersion strings or numbers |
images.* |
uploadImage a string; quotaGb, maxUploadGb, uploadTtlHours strings or numbers |
placement.* |
tolerateDedicatedWorkers a boolean, tolerations a list, affinity an object, nodeSelector string values |
networkPolicy.* |
enabled a boolean; gatewayFrom, egress.to and extraEgress lists of objects; egress.ports port numbers |
Keys outside this table reach the templates as given (api.images and api.supplyChain, which earlier schemas listed, are accepted and not read).
The templates stop an install with a message when:
| Values |
Message says |
database.driver other than pgsql |
RunR's pods share one PostgreSQL server |
secret.create: false with database.password, redis.password or mail.password |
there is no chart Secret to write it to: use *.existingSecret |
ingress.tls.mode: secret without ingress.tls.secretName |
name your TLS Secret |
reverb.replicas below 1 |
Reverb is required |
api.storage.accessModes / storageClassName or database.postgres.storageClassName other than those of the existing claim |
Kubernetes does not change them (moving the API volume) |
a cluster older than Kubernetes 1.23 (the chart's kubeVersion) |
the chart's minimum version |
Console and gateway#
domain#
| Key |
Default |
What it does |
domain |
"" |
Host name of the console, e.g. runr.example.com. Sets APP_URL and FRONTEND_URL to https://<domain>, the origin CORS allows; the Ingress is created only when it is set. With it the session cookie (Horizon dashboard) is sent over HTTPS only |
cors#
The console calls the API on its own origin and needs no CORS. Browser pages on other origins that call the API (/api/*) need their origin allowed.
| Key |
Default |
What it does |
cors.allowedOrigins |
[] |
Origins allowed besides https://<domain>, e.g. ["https://portal.example.com"] (CORS_ALLOWED_ORIGINS) |
gateway#
runr-gateway (Caddy) is the single entry point: it routes / to the UI, /api, /git, /mcp, /oauth2, /.well-known/oauth-*, /oauth/<provider>/callback, /horizon and /storage to the API and /ws to Reverb. It keeps the X-Forwarded-For and X-Forwarded-Proto of an ingress controller in clientIp.trustedProxies, so the API sees the client's address and builds https URLs.
| Key |
Default |
What it does |
gateway.replicas |
2 |
Replicas of runr-gateway; while gateway.hpa.enabled, the HPA owns the count |
gateway.affinity |
{} |
Affinity of the gateway pods instead of placement.affinity; empty: placement.affinity |
gateway.serviceType |
ClusterIP |
Type of the Service runr-gateway; LoadBalancer or NodePort expose the gateway without the Ingress (helm status prints how to reach it) |
gateway.port |
80 |
Port Caddy listens on and the port of the Service |
gateway.image.repository |
ghcr.io/brf-tech/runr/reqs/caddy |
Gateway image |
gateway.image.tag |
2-alpine |
|
gateway.image.pullPolicy |
IfNotPresent |
|
gateway.resources |
requests 50m / 64Mi, limits 250m / 128Mi |
|
gateway.hpa.enabled |
false |
HorizontalPodAutoscaler on CPU utilization |
gateway.hpa.minReplicas, maxReplicas, targetCPU |
2, 6, 70 |
Replica range and target CPU utilization in percent |
gateway.pdb.enabled |
false |
PodDisruptionBudget with maxUnavailable: 1 |
ingress#
The Ingress runr is created when ingress.enabled is true and domain is set: host <domain>, path / (Prefix) to runr-gateway on gateway.port.
| Key |
Default |
What it does |
ingress.enabled |
true |
Creates the Ingress |
ingress.className |
traefik |
ingressClassName of the Ingress; empty: none is set |
ingress.annotations |
{} |
Annotations of the Ingress. With ingress-nginx, the MCP streams need nginx.ingress.kubernetes.io/proxy-buffering: "off" and nginx.ingress.kubernetes.io/proxy-read-timeout: "900" |
ingress.tls.mode |
"" |
The console certificate, see below |
ingress.tls.enabled |
false |
Only with mode: "": adds a tls section with secretName |
ingress.tls.secretName |
"" |
Name of the TLS Secret; empty: runr-tls (letsencrypt, and mode: "" with enabled) or runr-tls-custom (custom); required with secret |
ingress.tls.clusterIssuer |
"" |
ClusterIssuer of the cert-manager annotation; empty: letsencrypt-prod (letsencrypt). With mode: "" the annotation is added only when this is set |
ingress.tls.certificate |
"" |
custom: the full PEM chain, server certificate first; PEM or base64-encoded PEM |
ingress.tls.privateKey |
"" |
custom: the certificate's unencrypted private key (PEM); never written to any output |
ingress.tls.mode |
Result |
letsencrypt |
Annotation cert-manager.io/cluster-issuer and a tls section; cert-manager issues the certificate into the Secret. Without the cert-manager.io/v1 API in the cluster the Ingress gets neither, like none |
custom |
The chart creates a kubernetes.io/tls Secret from certificate and privateKey; the install stops with an error when either is empty or not PEM |
secret |
A tls section with your kubernetes.io/tls Secret secretName in the release namespace; nothing is created |
none |
No tls section; the ingress controller serves its default certificate |
"" |
The keys of releases up to 0.3.5 apply: enabled adds the tls section with secretName, clusterIssuer adds the annotation |
Any other mode stops the install with an error. The console's TLS Secret name is also passed to the API (CONSOLE_TLS_SECRET_NAME), whose ServiceAccount may read that one Secret: a region's function domain can reuse the console certificate (regions.md).
clientIp#
The request path is client → ingress controller → runr-gateway → runr-api. The API takes the client address — per-IP rate limits, IPs in logs and audit records — from X-Forwarded-For only as far as these proxies wrote it.
| Key |
Default |
What it does |
clientIp.trustedProxies |
private_ranges |
Addresses the proxies connect from: CIDRs, comma-separated. runr-gateway keeps (and appends to) the X-Forwarded-For, and keeps the X-Forwarded-Proto, of an ingress controller in these ranges; runr-api (TRUSTED_PROXIES) accepts them from a gateway in them. private_ranges is RFC 1918, IPv6 ULA, loopback and link-local; a narrower value is the cluster's pod CIDR |
clientIp.proxyHops |
0 |
Proxies in front of runr-api that append to X-Forwarded-For (TRUSTED_PROXY_HOPS). 0 is automatic: 2 with the chart's Ingress (ingress controller and gateway), 1 without it (the gateway then replaces the header with the connecting address). Add 1 per HTTP proxy in front of the ingress controller whose header the controller keeps, e.g. an L7 load balancer; a load balancer that sends PROXY protocol adds none |
Application pods#
Replicas and resources with the chart defaults:
| Component |
Replicas |
Requests (CPU / memory) |
Limits (CPU / memory) |
runr-gateway |
2 |
50m / 64Mi |
250m / 128Mi |
runr-api |
2 |
200m / 256Mi |
1 / 1Gi |
runr-ui |
2 |
50m / 64Mi |
200m / 128Mi |
runr-reverb |
1 |
100m / 128Mi |
500m / 512Mi |
runr-horizon |
1 |
200m / 256Mi |
1 / 1Gi |
runr-scheduler |
1 |
50m / 64Mi |
256m / 256Mi |
runr-redis (bundled) |
1 |
100m / 128Mi |
500m / 512Mi |
runr-postgres (bundled) |
1 |
200m / 256Mi |
1 / 1Gi |
runr-mailpit (bundled) |
1 |
25m / 32Mi |
100m / 64Mi |
api#
The API image also runs Horizon, the scheduler, Reverb and the hook Jobs.
| Key |
Default |
What it does |
api.image.repository |
ghcr.io/brf-tech/runr/api |
API image |
api.image.tag |
latest |
Pin it to the release, e.g. 0.5.8 |
api.image.pullPolicy |
IfNotPresent |
|
api.replicas |
2 |
Replicas of runr-api; while api.hpa.enabled, the HPA owns the count |
api.port |
80 |
Port Octane listens on in the API container (RUNR_API_PORT), the container port, its probes and the Service's target port (the Service itself stays on 80) |
api.resources |
requests 200m / 256Mi, limits 1 / 1Gi |
|
api.storage.size |
10Gi |
Size of the PVC runr-api-storage; storage.application replaces it when set |
api.storage.accessModes |
[ReadWriteOnce] |
Access modes of runr-api-storage, set when the claim is created. ReadWriteOnce: the pods that mount it run on its node (podColocation); [ReadWriteMany] (with a ReadWriteMany StorageClass): on any node (Storage) |
api.storage.fixPermissions |
true |
Init container in every pod that mounts the API volume: chown -R 82:82 and chmod -R 775 (the www-data user; some volume types are created owned by root) |
api.storage.storageClassName |
not set |
StorageClass of runr-api-storage, set when the claim is created; unset: the cluster's default StorageClass |
api.affinity |
{} |
Affinity of the API pods instead of placement.affinity; empty: placement.affinity |
api.hpa.enabled |
false |
HorizontalPodAutoscaler on CPU utilization |
api.hpa.minReplicas, maxReplicas, targetCPU |
2, 6, 70 |
|
api.pdb.enabled |
false |
PodDisruptionBudget with maxUnavailable: 1, created with a ReadWriteMany API volume. With ReadWriteOnce the API pods cannot leave the volume's node, a budget would hold a drain of that node forever, and none is created (helm status says so) |
| Key |
Default |
What it does |
ui.image.repository |
ghcr.io/brf-tech/runr/ui |
UI image |
ui.image.tag |
latest |
Pin it to the release, e.g. 0.5.8 |
ui.image.multiTenantSuffix |
-mt |
Appended to the tag with edition: multi-tenant: the multi-tenant UI, built with the Tenancy and Auth Provider modules, is published as <version>-mt. With the tag latest, which has no multi-tenant variant, the multi-tenant edition runs <appVersion>-mt. "": the tag as it is (a UI image of your own that carries the modules) |
ui.image.pullPolicy |
IfNotPresent |
|
ui.replicas |
2 |
Replicas of runr-ui; while ui.hpa.enabled, the HPA owns the count |
ui.port |
80 |
Port Caddy listens on in the UI container (RUNR_UI_PORT), the container port, its probe and the Service's target port (the Service itself stays on 80) |
ui.affinity |
{} |
Affinity of the UI pods instead of placement.affinity; empty: placement.affinity |
ui.resources |
requests 50m / 64Mi, limits 200m / 128Mi |
|
ui.hpa.enabled |
false |
HorizontalPodAutoscaler on CPU utilization |
ui.hpa.minReplicas, maxReplicas, targetCPU |
2, 4, 70 |
|
ui.pdb.enabled |
false |
PodDisruptionBudget with maxUnavailable: 1 |
reverb#
Reverb, the WebSocket server, is always installed.
| Key |
Default |
What it does |
reverb.replicas |
1 |
Replicas of runr-reverb. From 2 on the replicas share broadcasts over Redis publish/subscribe (REVERB_SCALING_ENABLED); a shell, sandbox exec or region agent session is one WebSocket that stays on the replica it reached, which reads the session from Redis |
reverb.port |
8080 |
Port Reverb listens on (REVERB_SERVER_PORT), the port of the Service and the probes, and REVERB_PORT |
reverb.resources |
requests 100m / 128Mi, limits 500m / 512Mi |
|
horizon#
| Key |
Default |
What it does |
horizon.replicas |
1 |
Replicas of runr-horizon, the queue workers. The Deployment uses the Recreate strategy; a stopping pod lets its running jobs end (horizon:terminate) within a 60-second grace period |
horizon.resources |
requests 200m / 256Mi, limits 1 / 1Gi |
|
scheduler#
runr-scheduler runs the scheduled tasks with one replica.
| Key |
Default |
What it does |
scheduler.resources |
requests 50m / 64Mi, limits 256m / 256Mi |
|
app, queue and cache#
| Key |
Default |
What it does |
app.env |
production |
APP_ENV. Keep production: with local the Horizon dashboard admits every request, the API skips its configuration cache, Reverb logs every message and the install seeds development data |
app.debug |
"false" |
APP_DEBUG; "true" also makes Reverb log every connection and message |
app.logChannel |
stderr |
LOG_CHANNEL |
queue.connection |
redis |
QUEUE_CONNECTION |
cache.store |
redis |
CACHE_STORE |
| Key |
Default |
What it does |
extraEnv |
{} |
Additional environment variables (NAME: value) for the API, Horizon, scheduler and Reverb pods and the hook Jobs, written to the ConfigMap runr-config — no secrets. For settings the chart has no value of its own for, e.g. DB_SSLMODE; passwords come from database.*, redis.* and mail.* |
Data services#
database#
| Key |
Default |
What it does |
database.driver |
pgsql |
DB_CONNECTION: PostgreSQL, the database the API, Horizon, scheduler and Reverb pods share |
database.host |
"" |
Empty: the chart installs PostgreSQL (runr-postgres, host runr-postgres). Set: that external server, no PostgreSQL is installed |
database.port |
"5432" |
DB_PORT |
database.name |
runr |
DB_DATABASE; the database the chart's PostgreSQL creates |
database.user |
runr |
DB_USERNAME; the user the chart's PostgreSQL creates |
database.password |
"" |
The user's password, written to the chart's Secret as DB_PASSWORD (secret.create: true). Empty: the password kept from an earlier release, else generated on the first install |
database.existingSecret |
"" |
A Secret of yours with the password; the pods (and the chart's PostgreSQL) read DB_PASSWORD from it instead of the chart's Secret |
database.existingSecretPasswordKey |
password |
Key of the password in database.existingSecret |
database.postgres.image.repository |
ghcr.io/brf-tech/runr/reqs/postgres |
Image of the chart's PostgreSQL |
database.postgres.image.tag |
17-alpine |
|
database.postgres.storage |
10Gi |
Size of the PVC runr-postgres-data (ReadWriteOnce); storage.database replaces it when set |
database.postgres.storageClassName |
"" |
StorageClass of runr-postgres-data, set when the claim is created; empty: the cluster's default StorageClass |
database.postgres.fixPgdata |
true |
Init container: removes lost+found from the volume and keeps the data in the subdirectory pgdata (PGDATA), owned by UID 70 |
database.postgres.resources |
requests 200m / 256Mi, limits 1 / 1Gi |
|
database.postgres.pdb.enabled |
false |
PodDisruptionBudget with maxUnavailable: 1 |
The chart's PostgreSQL applies the password when it initialises an empty volume; a later change needs ALTER USER first (PostgreSQL).
redis#
| Key |
Default |
What it does |
redis.host |
"" |
Empty: the chart installs Redis (runr-redis, no volume). Set: that external server, no Redis is installed |
redis.port |
"6379" |
REDIS_PORT |
redis.username |
"" |
ACL user (REDIS_USERNAME); empty: the default user |
redis.password |
"" |
Password, written to the chart's Secret as REDIS_PASSWORD (secret.create: true) and read by the pods from there |
redis.existingSecret |
"" |
A Secret of yours with the password; the pods read REDIS_PASSWORD from it |
redis.existingSecretPasswordKey |
password |
Key of the password in redis.existingSecret |
redis.image.repository |
ghcr.io/brf-tech/runr/reqs/redis |
Image of the chart's Redis |
redis.image.tag |
7-alpine |
|
redis.resources |
requests 100m / 128Mi, limits 500m / 512Mi |
|
redis.pdb.enabled |
false |
PodDisruptionBudget with maxUnavailable: 1 |
With a password set (redis.password or redis.existingSecret), the chart's Redis requires it as well (--requirepass).
storage#
Shortcuts for the two disk sizes, e.g. from an install form. A PVC can grow, never shrink.
| Key |
Default |
What it does |
storage.application |
"" |
When set, the size of runr-api-storage instead of api.storage.size |
storage.database |
"" |
When set, the size of runr-postgres-data instead of database.postgres.storage |
mail and mailpit#
RunR always sends through SMTP (MAIL_MAILER=smtp).
| Key |
Default |
What it does |
mail.host |
"" |
Empty: Mailpit is installed and receives every message (runr-mailpit:1025). Set: that SMTP server, no Mailpit |
mail.port |
"1025" |
MAIL_PORT, used with mail.host |
mail.from |
noreply@runr.sh |
MAIL_FROM_ADDRESS |
mail.fromName |
RunR |
MAIL_FROM_NAME |
mail.username |
"" |
SMTP user (MAIL_USERNAME), used with mail.host |
mail.password |
"" |
SMTP password, written to the chart's Secret as MAIL_PASSWORD (secret.create: true) and read by the pods from there |
mail.existingSecret |
"" |
A Secret of yours with the SMTP password; the pods read MAIL_PASSWORD from it |
mail.existingSecretPasswordKey |
password |
Key of the password in mail.existingSecret |
mail.encryption |
"" |
"" or starttls: STARTTLS when the server offers it (TLS from the first byte on port 465); tls: TLS from the first byte on any port (MAIL_SCHEME=smtps) |
mailpit.image.repository |
ghcr.io/brf-tech/runr/reqs/mailpit |
Mailpit image |
mailpit.image.tag |
latest |
|
mailpit.uiPort |
8025 |
Port of Mailpit's web UI in the pod and the Service (the image serves it on 8025) |
mailpit.resources |
requests 25m / 32Mi, limits 100m / 64Mi |
|
Secret and first administrator#
secretName and secret#
| Key |
Default |
What it does |
secretName |
runr-secret |
The Secret every pod reads: APP_KEY, REVERB_APP_SECRET, DB_PASSWORD (unless database.existingSecret) and the optional ADMIN_PASSWORD, REDIS_PASSWORD, MAIL_PASSWORD, APP_PREVIOUS_KEYS, BILLING_WEBHOOK_SECRET, SIGNUP_CAPTCHA_SECRET |
secret.create |
true |
true: the chart creates the Secret. The first install generates APP_KEY, REVERB_APP_SECRET, DB_PASSWORD (unless database.password or database.existingSecret gives it) and ADMIN_PASSWORD; upgrades read the existing Secret (lookup) and keep them. database.password, redis.password and mail.password are written to it while set. false: create the Secret before installing; the chart only references it, and the passwords of the database, Redis and SMTP server come from DB_PASSWORD of it or from *.existingSecret |
secret.appKey |
"" |
Replaces APP_KEY (base64: and 32 random bytes, base64). Empty: generated at install and kept on upgrade. Used for a key rotation and a restore, and by tools that render without cluster access |
secret.previousAppKeys |
"" |
APP_PREVIOUS_KEYS, comma-separated earlier keys: values encrypted with them still decrypt. Present only while set |
APP_KEY encrypts the secrets stored in the database (security.md); the rotation procedure is in security.md. With secret.create: false the values secret.appKey, secret.previousAppKeys, admin.password, billing.webhookSecret and signup.captcha.secret are not used: put the keys into your Secret.
admin#
| Key |
Default |
What it does |
admin.username |
admin |
User name of the first administrator (ADMIN_USERNAME) |
admin.email |
admin@example.com |
Its e-mail address (ADMIN_EMAIL) |
admin.password |
"" |
Its password, written to ADMIN_PASSWORD with secret.create: true; empty: generated by the chart. With secret.create: false the password is ADMIN_PASSWORD of your Secret; without that key the bootstrap Job generates one into the Secret runr-admin (key password; the Job may read and patch that one Secret). Never written to a log |
bootstrap#
| Key |
Default |
What it does |
bootstrap.enabled |
true |
Renders the Job runr-bootstrap, a post-install and post-upgrade hook after the migration hooks. It deploys the agent again on regions whose agent has no token (agent:redeploy --missing), moves stored image references to the current image namespace (images:move-registry), brings the existing regions to the installed version (region:reconcile), gives accounts without a role the default role (rbac:assign-missing), in the multi-tenant edition converts an installation that ran single-tenant (bootstrap.tenancySetup), and creates the administrator above as a platform administrator when that account does not exist, no other platform administrator exists and the e-mail address is free. Existing accounts are left alone. false: the Job is not rendered |
bootstrap.tenancySetup |
true |
Multi-tenant edition: before the administrator check the Job converts an installation that ran single-tenant into its first tenant (php artisan tenancy:setup --force: name Default, the slug of the git remote owner; up to 8 minutes, activeDeadlineSeconds 1200 instead of 720). Fresh, converted and multi-tenant installations are left alone, a conversion that stopped continues. false: run tenancy:setup yourself, e.g. to review the plan with --dry-run or choose --name / --slug (Converting a single-organization installation) |
Edition#
| Key |
Default |
What it does |
edition |
single-tenant |
single-tenant: one organization. multi-tenant: the Tenancy and Auth Provider modules are on (TENANCY_ENABLED, AUTH_PROVIDER_ENABLED) and the UI runs the multi-tenant console, the tag with ui.image.multiTenantSuffix (<tag>-mt) |
tenancy.enabled |
false |
Turns the Tenancy module on in the API with edition: single-tenant as well |
authProvider.enabled |
false |
Turns the Auth Provider module on in the API with edition: single-tenant as well |
ticket.enabled |
true |
The Ticket module (support tickets) in the API and the hook Jobs (TICKET_ENABLED); values without the key keep it on. The console shows the ticket pages when its UI image is built with VITE_TICKET_ENABLED=true |
Editions: tenancy.md. Modules: modules.md.
quota#
The organization's CPU and memory ceiling, applied to every region's project namespace as a ResourceQuota and a LimitRange. In the multi-tenant edition it is the default tenant quota and the upper bound of the sum of tenant quotas on a region.
| Key |
Default |
What it does |
quota.cpu |
0 |
Ceiling in vCPU, e.g. 8 or 0.5 (QUOTA_CPU); 0 or empty: none |
quota.memoryGiB |
0 |
Ceiling in GiB (QUOTA_MEMORY_GIB); 0 or empty: none |
audit#
| Key |
Default |
What it does |
audit.retentionDays |
365 |
Audit events older than this are deleted daily; 0 keeps them (AUDIT_RETENTION_DAYS). The setting in Settings → Audit log overrides it |
audit.siemForwarding |
false |
Every audit event also goes to the API, Horizon and Reverb container log as one JSON line for a log collector (AUDIT_FORWARD_ENABLED) |
Details: security.md.
auth#
| Key |
Default |
What it does |
auth.loginTokenTtlDays |
30 |
Days a sign-in to the console or the CLI stays valid, counted from the sign-in; it applies to earlier sign-ins as well. 0: until signed out (RUNR_LOGIN_TOKEN_TTL_DAYS). API tokens keep the expiry they were created with |
billing#
The Billing module (billing.md): a monthly bill per project and, with tenancy, per tenant, for an external invoicing system. Off by default. A webhook saved in Settings → Billing → Webhook wins over the two webhook values.
| Key |
Default |
What it does |
billing.enabled |
false |
The Billing module in the API and the hook Jobs (BILLING_ENABLED); values without the key keep it off. The published UI images contain the billing screens and show them while the API reports billing on |
billing.currency |
USD |
ISO 4217 currency of every price and bill (BILLING_CURRENCY) |
billing.webhookUrl |
"" |
Billing events (bill.closed, bill.paid, fixed_request.approved, …) are POSTed here (BILLING_WEBHOOK_URL); empty: they are only kept for GET /api/billing/events |
billing.webhookSecret |
"" |
Signs the events (X-RunR-Signature); written to the Secret as BILLING_WEBHOOK_SECRET with secret.create: true |
branding#
The installation's brand, applied when the pods start (white-label.md). Per field, a value saved in Settings → Branding wins over these values, which win over the RunR default. Logos and the favicon take an https URL, a data:image/…;base64,… URI or a reference to a key of a ConfigMap or Secret in the release namespace ({configMapKeyRef: {name, key}} or {secretKeyRef: {name, key}}).
| Key |
Default |
What it does |
branding.productName |
"" |
Product name; empty: RunR |
branding.companyName |
"" |
Company name; empty: BRF Teknoloji |
branding.logo.light |
"" |
Logo for the light theme and e-mails |
branding.logo.dark |
"" |
Logo for the dark themes and the login page |
branding.logo.mark |
"" |
Square icon |
branding.favicon |
"" |
Favicon |
branding.colors.primary |
"" |
One base colour #rrggbb (shade 500; 300–900 derived) or the full 300–900 scale; empty: the RunR orange scale (#f97316) |
branding.colors.accent |
"" |
Accent colour; empty: #e63946 |
branding.loginHeadline, branding.loginText |
"" |
Texts of the login page |
branding.supportUrl, branding.supportEmail |
"" |
Support contact |
branding.footerLinks |
[] |
Footer links, [{label: Status, url: https://status.example.com}] |
branding.poweredBy |
true |
"Powered by RunR" on a re-branded login page and in e-mails |
branding.email.fromName |
"" |
Sender name of e-mails; empty: the product name |
branding.email.logo |
"" |
Logo in e-mails; empty: branding.logo.light |
branding.tenantOverrides |
true |
Multi-tenant: tenant owners may brand what their users see |
signup#
Defaults of the self-service signup (white-label.md); the values saved in Settings → Signup override them. Whether registration is open at all is the console setting registration_enabled (on by default, Settings → Platform → User Registration).
| Key |
Default |
What it does |
signup.emailVerification |
false |
The account is created when the link in the e-mail is opened |
signup.verificationTtlHours |
48 |
Lifetime of the verification link |
signup.allowedDomains |
"" |
Comma-separated e-mail domains allowed to sign up, "example.com,*.example.org"; empty: any |
signup.inviteOnly |
false |
Public signup answers 403; accounts come from tenant invitations and administrators |
signup.captcha.provider |
none |
none, turnstile or hcaptcha |
signup.captcha.siteKey |
"" |
The provider's site key |
signup.captcha.secret |
"" |
The provider's secret, written to the Secret as SIGNUP_CAPTCHA_SECRET with secret.create: true |
signup.termsUrl, signup.privacyUrl |
"" |
Terms of service and privacy policy links of the signup form |
signup.tenantAutoCreate |
true |
Multi-tenant: every signup gets its own tenant; the onboarding wizard (organization name, region) runs at the first sign-in |
signup.defaultQuota.cpuM |
2000 |
CPU quota of such a tenant, in millicores |
signup.defaultQuota.memoryMb |
4096 |
Memory quota of such a tenant, in MB |
signup.regions |
"" |
Platform region ids offered in the wizard, comma-separated; empty: every active region |
Regions and features#
knative#
| Key |
Default |
What it does |
knative.minK8sMinor |
"30" |
Lowest Kubernetes minor version accepted for a region (KNATIVE_MIN_K8S_MINOR) |
knative.traefikImage |
"" |
Image of the region Traefik (Knative ingress); empty: ghcr.io/brf-tech/runr/reqs/traefik at the version of the pinned chart. Point it at a registry the regions reach, with the same Traefik version; region:reconcile moves existing regions to it |
observability#
Tracing, request replay, uptime checks, the status page and alert delivery (observability.md).
| Key |
Default |
What it does |
observability.tracing.defaultMode |
managed |
managed, reuse, external or off: the tracing backend of regions without their own setting. With installWithServing it also decides whether region:reconcile installs the backend on such a region |
observability.tracing.installWithServing |
true |
Install the tracing backend together with Knative Serving |
observability.tracing.knativeSampleRate |
0.1 |
Span sampling of the Knative activator and queue-proxy; a region setting overrides it |
observability.tracing.tempoRetentionHours |
72 |
Trace retention of Tempo |
observability.tracing.tempoStorage |
10Gi |
Size of Tempo's volume |
observability.tracing.collectorChartVersion |
"" |
Chart version of open-telemetry/opentelemetry-collector; empty: the pinned 0.176.0 (collector 0.162.0) |
observability.tracing.tempoChartVersion |
"" |
Chart version of grafana-community/tempo; empty: the pinned 3.2.0 (Tempo 3.1.0) |
observability.tracing.collectorImage |
"" |
Collector image; empty: ghcr.io/brf-tech/runr/reqs/opentelemetry-collector-contrib at the pinned version |
observability.tracing.tempoImage |
"" |
Tempo image; empty: ghcr.io/brf-tech/runr/reqs/tempo at the pinned version |
observability.replay.maxBodyBytes |
65536 |
Project default of the captured body size; projects choose up to 1 MiB |
observability.replay.retentionDays |
7 |
Project default of the capture retention; projects choose up to 30 |
observability.replay.maxCaptures |
500 |
Captures per project, the oldest dropped first |
observability.replay.ingestUrl |
"" |
RunR as seen from region pods; empty: the region's agent URL |
observability.uptime.minIntervalSeconds |
30 |
Shortest interval of an uptime check |
observability.uptime.resultsRetentionDays |
90 |
Retention of check results |
observability.uptime.allowPrivateTargets |
false |
true: checks of an absolute URL may target private addresses |
observability.statusPage.enabled |
true |
The public status page |
observability.alerts.deliveryTries |
4 |
Delivery attempts of an alert |
observability.alerts.allowPrivateTargets |
false |
true: webhook, Slack and Teams URLs may be private addresses |
sandboxApi#
Isolated execution of untrusted code (sandbox-api.md). Region settings, warm pools and organization quotas are set in Settings → Sandboxes; these are the platform defaults and ceilings.
| Key |
Default |
What it does |
sandboxApi.enabled |
true |
The Sandbox API |
sandboxApi.namespacePrefix |
runr-sbx |
Namespace <prefix>-<organization> per region |
sandboxApi.defaultRuntime |
python |
Runtime of a sandbox created without one |
sandboxApi.allowCustomImages |
true |
Sandboxes may name an image instead of a runtime of the catalog |
sandboxApi.defaults.cpuM, memoryMb, diskMb |
500, 512, 1024 |
Default size of a sandbox |
sandboxApi.defaults.ttlSeconds, idleTimeoutSeconds |
900, 600 |
Default lifetime and idle timeout |
sandboxApi.limits.maxCpuM, maxMemoryMb, maxDiskMb, maxTtlSeconds |
4000, 8192, 10240, 86400 |
Ceilings per sandbox; a region may lower them |
sandboxApi.quota.maxConcurrent, maxCpuM, maxMemoryMb |
20, 8000, 16384 |
Per organization unless set in Settings → Sandboxes; 0 = unlimited |
sandboxApi.quota.maxSandboxSecondsMonth, maxPoolSize |
0, 10 |
Sandbox seconds per month and warm pool size per organization; 0 = unlimited |
sandboxApi.exec.defaultTimeout, maxTimeout |
60, 3600 |
Timeouts of a command, in seconds |
sandboxApi.files.maxUploadBytes, maxDownloadBytes |
52428800, 52428800 |
Largest file upload and download |
sandboxApi.poolMaxAgeSeconds |
3600 |
Warm pods are replaced after this |
sandboxApi.retentionDays |
30 |
Ended sandboxes are removed after this |
gitDeploy#
Every project gets a git remote https://<domain>/git/<organization>/<project>.git, served by the API behind the gateway's /git route; the bare repositories live on the API volume (git-remotes/). Providers are added in Settings → Git providers (git-deploy.md).
| Key |
Default |
What it does |
gitDeploy.enabled |
true |
Project git remotes and the gateway's /git route |
gitDeploy.maxPushMb |
50 |
Largest pack one push may send; stay below the API's 64 MB request limit |
gitDeploy.remoteOwner |
runr |
Owner segment of remote URLs in the single-tenant edition |
gitDeploy.webhookBaseUrl |
"" |
Base URL of provider webhooks; empty: https://<domain> |
gitDeploy.previews.limit |
3 |
Default number of live previews per project |
gitDeploy.previews.ttlHours |
72 |
Default hours a preview lives without a new commit |
gitDeploy.previews.maxLimit |
20 |
Highest per-project preview limit |
supplyChain#
SBOMs, vulnerability scans, signatures and the admission policy per region (supply-chain.md). What runs in a region is set in the console (Region → Supply chain); these are the platform-wide defaults. Empty values keep the application defaults.
| Key |
Default |
What it does |
supplyChain.enabled |
true |
Every image RunR builds gets its supply-chain run |
supplyChain.toolsImage |
"" |
Tools image; empty: ghcr.io/brf-tech/runr/supply-chain:1.0.0 |
supplyChain.jobNamespace |
"" |
Namespace of the Jobs and the region signing key; empty: runr-supply-chain |
supplyChain.rescanHours |
"" |
Re-scan interval of deployed images for new regions; empty: 24 |
supplyChain.trivyDbSource |
"" |
Mirror source of the vulnerability database; empty: mirror.gcr.io/aquasec/trivy-db:2 |
supplyChain.trivyJavaDbSource |
"" |
Mirror source of the Java database; empty: mirror.gcr.io/aquasec/trivy-java-db:1 |
supplyChain.trivyDbRefreshHours |
"" |
Refresh interval of the mirrored databases; empty: 12 |
supplyChain.policyController.helmRepo |
"" |
Helm repository of the sigstore policy-controller; empty: https://sigstore.github.io/helm-charts |
supplyChain.policyController.chartVersion |
"" |
Its chart version; empty: the version the release pins |
supplyChain.policyController.extraArgs |
"" |
Webhook flags, e.g. "disable-tuf=true" for regions without internet access |
images#
Container images and .wasm modules uploaded into a region's registry (images.md). Empty values keep the application defaults.
| Key |
Default |
What it does |
images.uploadImage |
"" |
Image of the upload receiver the regions pull; empty: ghcr.io/brf-tech/runr/image-upload:1.0.0 |
images.quotaGb |
"" |
Storage of uploaded images per organization over every region; empty: 50, 0 = unlimited |
images.maxUploadGb |
"" |
Largest file of one upload; empty: 20 |
images.uploadTtlHours |
"" |
An upload not completed within this time is aborted; empty: 24 |
GPU support and Hugging Face models (ai.md, huggingface.md). Point the images and charts at a registry the regions reach for air-gapped installations.
| Key |
Default |
What it does |
ai.huggingface.endpoint |
https://huggingface.co |
Hub API of the console's model browser and of online model downloads; a mirror with the same API works |
ai.huggingface.browse |
true |
false: the console does not call the Hub; models are uploaded or imported |
ai.images.hfServe |
ghcr.io/brf-tech/runr/hf-serve:1.1.1 |
hf-serve image |
ai.images.hfServeCuda |
ghcr.io/brf-tech/runr/hf-serve:1.1.1-cuda |
hf-serve CUDA image |
ai.images.vllm |
docker.io/vllm/vllm-openai:v0.6.6 |
vLLM image |
ai.images.tgi |
ghcr.io/huggingface/text-generation-inference:3.0.1 |
TGI image |
ai.images.teiCpu |
ghcr.io/huggingface/text-embeddings-inference:cpu-1.9.4 |
TEI image for CPU |
ai.images.teiGpu |
ghcr.io/huggingface/text-embeddings-inference:{arch}1.9.4 |
TEI image for GPU; {arch} follows the GPU's compute capability |
ai.images.oras |
ghcr.io/oras-project/oras:v1.2.0 |
ORAS image |
ai.gpu.devicePluginChartVersion |
"0.17.0" |
NVIDIA device plugin chart version |
ai.gpu.operatorChartVersion |
"v24.9.2" |
NVIDIA GPU Operator chart version |
ai.gpu.defaultTimeout |
300 |
Default timeout in seconds of GPU projects and model services |
ai.gpu.progressDeadline |
1800s |
Start-up deadline of GPU workloads and model services (image pull and model load) |
ai.gpu.scaleToZeroRetention |
5m |
How long GPU pods stay after the last request before they scale to zero |
ai.modelStorage.sizeGi |
100 |
Default size of a region's model volume (PVC runr-models) |
ai.modelStorage.accessMode |
ReadWriteMany |
Its default access mode |
ai.modelStorage.storageClass |
"" |
Its default StorageClass; each region can change these before its first model |
bindings#
Managed KV, object storage and Postgres that projects attach to (bindings.md). The data services are installed per region from the console (Region → Data services).
| Key |
Default |
What it does |
bindings.namespace |
runr-data |
Namespace of the data services in each region |
bindings.retentionDays |
7 |
A deleted binding keeps its data this long (restore possible) |
bindings.rotationGraceSeconds |
600 |
Old credentials keep working this long after a rotation |
bindings.storageClass |
"" |
StorageClass of the data services' PVCs; empty: the cluster default |
bindings.quotas.maxBindings |
20 |
Bindings per organization (per tenant in multi-tenant installs); 0 = unlimited |
bindings.quotas.kvMb, objectGb, postgresGb |
1024, 100, 50 |
KV, object and Postgres storage per organization; 0 = unlimited |
bindings.quotas.dedicatedPostgres |
2 |
Dedicated Postgres instances per organization; 0 = unlimited |
bindings.kv.maxmemoryMb |
512 |
Memory of the managed KV Redis (noeviction) |
bindings.kv.storage |
2Gi |
Its AOF volume |
bindings.object.image |
ghcr.io/brf-tech/runr/reqs/garage:v2.1.0 |
Object storage image (Garage) |
bindings.object.storage |
20Gi |
Its volume |
bindings.object.s3Region |
garage |
S3 region name |
bindings.postgres.cnpgChartVersion |
"0.26.0" |
CloudNativePG chart version |
bindings.postgres.image |
ghcr.io/cloudnative-pg/postgresql:17.5 |
PostgreSQL image of the managed instances |
bindings.postgres.sharedInstances |
1 |
Instances of the shared Postgres cluster |
bindings.postgres.sharedStorage |
10Gi |
Its volume |
bindings.postgres.backupRetention |
7d |
Backup retention |
Scheduling and cluster#
| Key |
Default |
What it does |
podColocation |
true |
With a ReadWriteOnce API volume (api.storage.accessModes), the pods that mount it — API, Horizon, scheduler, Reverb and the hook Jobs — get the label runr/storage-group: api and a required pod affinity on it per node, so they run on the volume's node. No effect with ReadWriteMany |
placement.tolerateDedicatedWorkers |
true |
Every RunR pod tolerates the taint runr.dev/dedicated=projects:NoSchedule, which a region on this cluster puts on its dedicated worker nodes, and prefers the other nodes (preferred node affinity); a pod whose volume is bound to such a node still starts there |
placement.tolerations |
[] |
Tolerations added to every RunR pod |
placement.affinity |
{} |
Affinity of every RunR pod, unless gateway.affinity, api.affinity or ui.affinity replaces it for that component; podColocation and tolerateDedicatedWorkers add their terms to it |
placement.nodeSelector |
{} |
nodeSelector of every RunR pod, e.g. {node-role.kubernetes.io/infra: "true"} |
networkPolicy.enabled |
false |
NetworkPolicies that allow exactly the connections of Network policies and deny the rest (needs a CNI that enforces NetworkPolicy) |
networkPolicy.gatewayFrom |
[] |
NetworkPolicy peers that may reach runr-gateway, e.g. the ingress controller's namespace; empty: any source |
networkPolicy.egress.to |
0.0.0.0/0, ::/0 |
Peers of the outbound connections of the API, Horizon, scheduler and Reverb pods and the hook Jobs (regions, registries, Helm repositories, git providers, alert targets) |
networkPolicy.egress.ports |
[80, 443, 6443] |
TCP ports of those outbound connections; empty: every port |
networkPolicy.extraEgress |
[] |
Further egress rules (NetworkPolicyEgressRule) of those pods |
imagePullSecrets |
[] |
Pull secrets added to every pod and hook Job: ["regcred"] or [{name: regcred}] |
imagePullSecretsFromDefaultServiceAccount |
true |
Also adds the pull secrets of the default ServiceAccount of the release namespace (read with lookup, so only when Helm runs against the cluster) |
initImage.repository |
ghcr.io/brf-tech/runr/reqs/busybox |
Image of the init containers (fix-permissions, fix-pgdata) |
initImage.tag |
latest |
The mirror carries only latest |
namespace |
"" |
Install namespace; empty: the Helm release namespace |
createNamespace |
false |
true: the chart creates Namespace objects for the install namespace and projectNamespace |
projectNamespace |
runr-projects |
Name of the second Namespace that createNamespace creates |
deployTimestamp |
"" |
When set, written as an annotation on the pod templates of the gateway, API, UI, Horizon, scheduler and Reverb: a new value rolls them, also with an unchanged image tag |