# Helm values

Every value of the GoRunner Helm chart, with its default.

Set them in your values file, or with `--set`. `helm install` and `helm upgrade` check them first, and say in one sentence what is wrong. To see them with their comments:

```bash
helm show values oci://ghcr.io/ralve-org/charts/gorunner --version 2026.10.30
```

Below, `<release>` is the name the chart gives its objects: `gorunner` for a release named `gorunner`, so `gorunner-api`. A setting with a value of its own here, such as `worker.concurrency`, is refused under `config`.

| Name | Default | What it does |
| --- | --- | --- |
| **The image** |   |   |
| `image.repository` | `ghcr.io/ralve-org/gorunner-selfhost` | Where the image comes from. Change it for a mirror inside your company. |
| `image.tag` | the chart’s release | The version to run. Leave it empty: each chart version runs its own release. |
| `image.digest` | — | `sha256:…` pins the exact image, and wins over the tag. |
| `image.pullPolicy` | `IfNotPresent` | When the cluster downloads the image again. |
| `imagePullSecrets` | `[]` | The Secret with the registry’s user name and token, such as `[{name: ghcr}]`. GoRunner’s image is private, so this is needed. The chart is private for now too: sign Helm in first (`helm registry login ghcr.io`). Every pod that runs GoRunner’s image uses it, the bundled file store’s relay included. |
| **Where people reach it** |   |   |
| `publicUrl` | from ingress.host | The address people open. Every email, approval page and OAuth redirect is built from it. Empty: `ingress.host`, with https when `ingress.tls.enabled`. |
| `ingress.enabled` | `true` | Make an Ingress. Off: route to the `<release>-api` Service yourself, on port 80. |
| `ingress.className` | `nginx` | The IngressClass, such as `nginx`, `traefik` or `alb`. Empty: the cluster’s default. |
| `ingress.host` | — | The domain name. Required with an Ingress. |
| `ingress.path` | `/` | GoRunner needs the whole host; a sub-path isn’t supported. With `ingress.pathType`, `Prefix`. |
| `ingress.tls.enabled` | `false` | Serve the address over https. Turn it on unless something in front ends https. |
| `ingress.tls.secretName` | \<release>-tls | The certificate’s Secret, or the one cert-manager writes. |
| `ingress.annotations` | `{}` | Your own, such as `cert-manager.io/cluster-issuer`. They win over the chart’s. |
| `ingress.nginxDefaults` | `true` | With `className: nginx`: no response buffering, an hour’s read and send timeout, and a 6 MiB body. |
| `api.trustedProxies` | — | Who may name the caller’s address. Empty trusts every private address, safe only while the network policy is on. With `networkPolicy.enabled` off, it is required and must name your controller’s own addresses; `private_ranges` is then refused. |
| **The first admin and sign-in** |   |   |
| `admin.email` | — | Only this address can make the first account. Optional without `existingSecret`; with it, see `admin.allowOpenFirstSignup`. It does nothing once an account exists. |
| `admin.allowOpenFirstSignup` | `false` | With `existingSecret` and no `admin.email`: `true` lets whoever has the setup code be first. `false`: the first admin must be `ADMIN_EMAIL` in your Secret, and without that key nobody can be. |
| `auth.allowSignup` | `false` | `false`: people join by invitation. `true`: anyone who can open the address can make an account. |
| `deployMode` | `selfhosted` | Leave it. |
| **Secrets** |   |   |
| `existingSecret` | — | Your own Secret, its keys the setting names. Set, the chart writes none, and every secret value here is ignored. Keys: `ENCRYPTION_KEY` always; `ADMIN_EMAIL` unless `admin.email` is set; `DATABASE_URL` (or `POSTGRES_PASSWORD`) and `REDIS_URL` (or `REDIS_PASSWORD`) unless bundled; `S3_ACCESS_KEY`, `S3_SECRET_KEY`; `RESEND_API_KEY` or `SMTP_PASSWORD`; `HTTPS_PROXY`, `HTTP_PROXY`; and if used `LICENSE_KEY`, `PLATFORM_ADMIN_SECRET`, `METRICS_TOKEN`, `SENTRY_DSN`, `OTEL_EXPORTER_OTLP_HEADERS`, `OAUTH_MANAGED_<PROVIDER>_CLIENT_SECRET`. After changing it, restart the pods: `kubectl rollout restart deployment -l app.kubernetes.io/instance=<release>`. |
| `encryptionKey` | — | 64 hex characters (`openssl rand -hex 32`). Opens every stored credential. The chart never makes one up. **Warning** Keep it apart from the database’s backups, and never change it once there is data. |
| `licenseKey` | — | The license. Optional: the first admin is asked for one after signing in. One loaded in the dashboard comes first while in force. |
| `platformAdminSecret` | — | The key to the operator console at `/admin`. Empty keeps it closed; set it only when GoRunner asks. |
| `secretConfig` | `{}` | Any other secret setting by its name, such as `OAUTH_MANAGED_GOOGLE_CLIENT_SECRET`. |
| **Mail** |   |   |
| `mail.backend` | `none` | `none`: people sign in with a password. `resend` or `smtp`: sign-in by emailed link. |
| `mail.from` | — | The sender, on a domain your mail service may send for. |
| `mail.resendApiKey` | — | With `backend: resend`. |
| `mail.smtp.host` | — | With `backend: smtp`. |
| `mail.smtp.port` | `587` | `587`, or `465` for TLS from the first byte. |
| `mail.smtp.secure` | — | `starttls`, `tls` or `none`. Empty: `tls` on 465, otherwise STARTTLS. |
| `mail.smtp.user` | — | With `mail.smtp.password`. Leave both empty for a server with no sign-in. |
| **PostgreSQL** |   |   |
| `postgresql.url` | — | Your database, PostgreSQL 15 or newer, as one address. It wins over the fields below. |
| `postgresql.host` | — | The host alone, without its port. With `port` (`5432`), `database` (`gorunner`), `user` and `password`. |
| `postgresql.sslmode` | `require` | `disable`, `require`, `verify-ca` or `verify-full`. |
| `postgresql.caFile` | — | The CA for `verify-ca` or `verify-full`, as a path in the pods, such as `/certs/rds.pem`. |
| `postgresql.maxConnectionsPerProcess` | empty: 50 | The most connections each pod keeps (`DB_MAX_CONNS`). All pods, plus one of each while an upgrade rolls, must fit `max_connections`. |
| `postgresql.adminHeadroom` | empty: 5 | Connections the check keeps free for backups and an admin, at least 5. |
| `postgresql.bundled.enabled` | `false` | Run the chart’s own PostgreSQL 15.6: one pod, an 8Gi volume, no backups. Evaluation only. |
| **Redis** |   |   |
| `redis.url` | — | Your Redis 6.2 or newer as one address, `rediss://` for TLS. Not in cluster mode, with `noeviction`. |
| `redis.host` | — | Separate fields: `port` (`6379`), `db` (`0`), `user`, `password`. |
| `redis.tls` | `false` | `true` for an encrypted connection. |
| `redis.bundled.enabled` | `false` | Run the chart’s own Redis: one pod, a 2Gi volume, no replica. Evaluation only. |
| **Object storage** |   |   |
| `objectStorage.mode` | `none` | `none`: step data stays in PostgreSQL. `external`: your bucket. `bundled`: one SeaweedFS pod, evaluation only. |
| `objectStorage.bucket` | `gorunner` | With `objectStorage.region`, `us-east-1`. |
| `objectStorage.inlineThresholdKB` | `0` | Step data larger than this goes to the bucket. `0`: all of it. |
| `objectStorage.opTimeoutSeconds` | empty: 15 | How long one storage call may take. `maxAttempts`: how often it is tried, 4. |
| `objectStorage.external.endpoint` | — | Your store’s address. |
| `objectStorage.external.forcePathStyle` | `false` | `true` for MinIO and SeaweedFS. |
| `objectStorage.external.accessKey` | — | With `secretKey`. |
| **Bundled stores (evaluation)** |   |   |
| `postgresql.bundled.image.tag` | `15.6` | `docker.io/library/postgres` (`image.repository`). `image.digest` is empty: a new image string would restart the database. Set it yourself to pin one. |
| `redis.bundled.image.tag` | `7-alpine` | `docker.io/library/redis`. The release sets `image.digest` to the one the tag named when it was tested. |
| `objectStorage.bundled.image.tag` | `4.33` | `docker.io/chrislusf/seaweedfs`, with its digest set by the release. |
| `postgresql.bundled.persistence.size` | `8Gi` | Redis `2Gi`, SeaweedFS `10Gi`. Each also takes `persistence.storageClass` (empty: the cluster’s default) and `resources`. |
| `postgresql.bundled.password` | made once | Also `redis.bundled.password` and `objectStorage.bundled.accessKey` / `secretKey`. Empty: made once and kept in the `<release>-bundled` Secret, which outlives an uninstall. A first install derives them from `encryptionKey`, so `helm template` (Argo CD, Flux) renders the same ones every time. With `existingSecret` and `helm template`, set them yourself. |
| **The app** |   |   |
| `api.replicas` | `2` | Keep at least 2, so a drain or a crash never leaves nobody to answer. |
| `api.resources` | 100m CPU, 256Mi–1Gi | CPU and memory for each. |
| `api.runRetentionDays` | `90` | Runs that finished longer ago than this are deleted hourly, with their data. Each workflow keeps its newest finished run in each environment. |
| `api.rateLimits.api` | empty: 1200 | Requests a minute per credential. Also `webhook` 3000, `oauth` 120, `embedApi` 3000, `embedAccount` 300, `embedOauthRequest` 600 (from v2026.10.31), `embedPerson` 20, `embedRenew` 4. |
| `api.embedSessionMaxHours` | empty: 12 | How long the embed renews its own session, 1 to 24 hours. |
| `api.corsOrigins` | — | Other websites that may call the API from a browser, separated by commas. |
| `api.oauthPublicCors` | empty: on | `off`: AI apps’ sign-in addresses (discovery, register, token, revoke) keep the API’s own CORS instead of answering any website. For an installation reached only from your own network (`OAUTH_PUBLIC_CORS`, from v2026.10.31). |
| `api.oauthExtraRedirectUris` | — | More app callbacks AI apps may register: whole addresses, separated by commas, no query (`OAUTH_EXTRA_REDIRECT_URIS`, from v2026.10.31). |
| `api.accessLog` | `false` | Adds the caller’s address and browser to each request’s line (`ACCESS_LOG`). |
| `api.healthDetails` | — | `public`: health checks show their details to anyone (`HEALTH_DETAILS`). |
| `api.terminationGracePeriodSeconds` | `60` | When a pod stops, how long it has to finish requests, after `preStopSeconds` (10). |
| `api.pdb.enabled` | `true` | Keep at least `minAvailable` (1) app pod through node drains. |
| `api.service.type` | `ClusterIP` | The Service in front of the app, on `port` 80, with `annotations` of your own. |
| `api.extraEnv` | `[]` | More environment variables for the app pods, as a Kubernetes `env` list. |
| `api.nodeSelector` | `{}` | Also `tolerations`, `affinity`, `topologySpreadConstraints`, `priorityClassName`, `podAnnotations`, `podLabels`. |
| **The workers** |   |   |
| `worker.replicas` | `1` | More workers is the safer way to grow than larger ones. |
| `worker.concurrency` | `4` | Runs one worker runs at once. Raise its memory limit with it. |
| `worker.resources` | 250m CPU, 512Mi–2Gi | CPU and memory for each. |
| `worker.keelConcurrency` | `2` | Keel conversations one worker serves at once. `0` turns Keel off on it. |
| `worker.keelCompile` | — | `off`: Keel builds without its compile step. Leave it unless GoRunner asks. |
| `worker.notifyConcurrency` | empty: 4 | Notifications to your server one worker sends at once. |
| `worker.interactiveConcurrency` | empty: 2 | **Test step** and connection tests one worker serves at once. |
| `worker.terminationGracePeriodSeconds` | `330` | How long a stopping worker may finish its runs. After that, another worker picks them up. |
| `worker.extraEnv` | `[]` | And the same placement values as the app. |
| **Workflows** |   |   |
| `workflows.privateNetworks` | — | Private addresses steps may reach: empty for none, `all`, or ranges. See [Outbound access](https://docs.gorunner.io/self-host/outbound-access). |
| `workflows.waitingPerOrg` | empty: 10000 | Runs of one organization that may wait before new ones are refused. `0`: no cap. |
| `workflows.waitingPerWorkflow` | empty: 1000 | The same for one workflow, editor test runs included. |
| **Migrations and the check** |   |   |
| `migrate.job.enabled` | `true` | Run the migrations Job at every install and upgrade. Leave it on. |
| `migrate.job.args` | `[]` | Extra flags, only when GoRunner asks. |
| `migrate.job.backoffLimit` | `6` | Retries. `activeDeadlineSeconds`: 900 in all. |
| `migrate.job.ttlSecondsAfterFinished` | `86400` | The finished Job and its log go after a day. The Job is named after the Helm revision and the image (`<release>-migrate-r3-…`), so `helm template` and GitOps tools make a new Job for every version instead of changing a finished one. Read its check with `kubectl logs job/<name> -c check`. |
| `migrate.resources` | 50m CPU, 64Mi–256Mi | CPU and memory for the Job. |
| `check.enabled` | `true` | A problem the check finds fails the Job. `false`: only logged, except one that protects data. |
| **A company proxy** |   |   |
| `proxy.https` | — | With `proxy.http`: the proxy’s address, kept in the Secret. |
| `proxy.noProxy` | — | Names reached directly. GoRunner adds its own, the cluster’s `.svc` and `.cluster.local` among them. |
| `proxy.connect` | — | `name` or `ip`. See [By name, or by address](https://docs.gorunner.io/self-host/company-proxy#asking-for-a-site-by-name-or-by-address). |
| **Certificates and extra volumes** |   |   |
| `extraVolumes` | `[]` | More volumes on every pod, with `extraVolumeMounts`. Every `.pem` mounted at `/certs` is trusted. |
| **Monitoring** |   |   |
| `telemetry.logLevel` | — | `LOG_LEVEL`. And `logFormat`: `LOG_FORMAT`. |
| `telemetry.otel.endpoint` | — | `OTEL_EXPORTER_OTLP_ENDPOINT`. Each `telemetry.otel.*` value is the `OTEL_*` setting of the same meaning; `headers` is kept in the Secret. |
| `telemetry.metrics.enabled` | `false` | `METRICS_ENABLED`: `/metrics` on port 9464 of every pod, with `token` and `workflowLabels`. |
| `telemetry.sentry.dsn` | — | `SENTRY_DSN`, kept in the Secret, with `environment`. |
| `telemetry.clientErrorsEnabled` | — | `CLIENT_ERRORS_ENABLED`. Also `auditRetentionDays`, `auditCredentialUseRetentionDays`, `auditIpRetentionDays` and `eventStreamingEnabled`. |
| **Any other setting** |   |   |
| `config` | `{}` | Any [setting](https://docs.gorunner.io/self-host/settings) the values above don’t cover, by its name, such as `OUTBOUND_ALLOW_PRIVATE: "true"`. |
| **Kubernetes** |   |   |
| `serviceAccount.create` | `true` | GoRunner never calls the Kubernetes API, so the account holds no token. Also `name` and `annotations`. |
| `podSecurityContext` | user 10001, non-root | With the default seccomp profile. |
| `securityContext` | read-only root | No privilege escalation, every capability dropped. |
| `networkPolicy.enabled` | `true` | Only your controller and `helm test` reach the app; the workers accept nothing. Needs a plugin that enforces NetworkPolicy. |
| `networkPolicy.apiFrom` | the ingress-nginx namespace | Who may reach the app. Change it for any other controller, or the app answers nobody. |
| `networkPolicy.metricsFrom` | `[]` | Who may read `/metrics` on port 9464. |
| `tests.enabled` | `true` | The checks `helm test` runs. |
| `nameOverride` | — | With `fullnameOverride`: other names for the chart’s objects. |

Related: [Install on Kubernetes](https://docs.gorunner.io/self-host/install-kubernetes) · [Settings (.env)](https://docs.gorunner.io/self-host/settings) · [Your own database, Redis and storage](https://docs.gorunner.io/self-host/own-stores)

Source: https://docs.gorunner.io/self-host/helm-values
