Helm Values
The Helm values of a module on Dedicated come from two places:
- Your service repository. When you release, Runway merges the values files in your Runway directory into the module’s default values, and stores them in the Module Catalog entry for that release.
- The Tenant Model. When Runway deploys the module to a tenant, it merges the tenant’s
values_override.helm.valuesover the default values from the catalog entry.
Runway then expands templates in the merged values, so that one set of default values can work for every tenant.
The Helm values themselves are documented in the Fairway values reference.
Values files in your service repository
Section titled “Values files in your service repository”Runway reads these files from your Runway directory (.runway by default), and merges them in this order:
| Order | Source | Description |
|---|---|---|
| 1 | Fairway | Fairway’s built-in defaults. |
| 2 | fairway.yaml |
spec.values from the Fairway manifest. |
| 3 | values.yaml |
Values shared by all runtimes. Optional. |
| 4 | values-dedicated.yaml |
Values that only apply to Dedicated. Optional. |
| 5 | spec.extraValueFiles in the module manifest |
Extra values files, in the order they are listed. |
Put values that apply to every runtime in values.yaml, and values that only make sense on Dedicated, such as
templates, in values-dedicated.yaml.
Extra values files
Section titled “Extra values files”spec.extraValueFiles in the module manifest lists more values files, relative to the Runway directory:
apiVersion: runway/v3kind: RunwayManifestmetadata: name: example-service-dedicatedspec: extraValueFiles: - values-common.yaml - "values-{{ .Runtime }}-features.yaml"File names are Go text/template templates, with these fields:
| Field | Value on Dedicated | Description |
|---|---|---|
.Runtime |
dedicated |
The runtime: gke, eks, or dedicated. |
.ServiceID |
The Runway service ID | metadata.name of the module manifest, for example example-service-dedicated. |
.Environment |
dedicated |
The environment, such as staging or production on GKE. |
A file whose name contains a template is skipped if it doesn’t exist. A file with a literal name, such as
values-common.yaml, must exist.
Merge semantics
Section titled “Merge semantics”Each layer is merged over the result of the previous layers, the same way Helm merges values files:
- Objects (maps) are merged key by key, recursively. Keys that only exist in the earlier layer are kept.
- Scalars (strings, numbers, booleans) are replaced.
- Lists are replaced as a whole. They are not concatenated, and their entries are not merged.
This also applies to appConfig.data, which is free-form.
environment: LOG_LEVEL: info REGION: us-east-1secretEnvFrom: - shared-secrets - extra-secretsenvironment: LOG_LEVEL: warnsecretEnvFrom: - dedicated-secretsenvironment: LOG_LEVEL: warn REGION: us-east-1secretEnvFrom: - dedicated-secretsSee Values files in the Helm documentation for details.
Overrides in the Tenant Model
Section titled “Overrides in the Tenant Model”A tenant can override any of the default values with
values_override.helm.values in its Tenant Model. Runway merges them over
the default values from the catalog entry when it deploys the module, with the same merge semantics.
{ "modular_features": { "modules": { "example-service-dedicated": { "enabled": true, "version": "v1.25.0", "values_override": { "helm": { "values": { "environment": { "LOG_LEVEL": "debug" } } } } } } }}Overrides are an escape hatch for a single tenant, for example to raise the log level while debugging. They are not how a module is configured: values that every tenant needs belong in your service repository, so that they are released, reviewed, and tested with the module. If a value only differs between tenants in a way that a template can express, use a template instead of an override.
Runway owns some values, such as the secretRef of the S3 bucket,
the PostgreSQL instance, and the
Redis instance. It sets them after the merge,
and warns if it overwrites a value from the default values or from the Tenant Model.
Application config
Section titled “Application config”appConfig.data is your service’s own configuration. Fairway doesn’t interpret it: it can be any tree of objects,
lists, and scalars, in whatever structure your service reads. Set it in your values files like any other value:
appConfig: data: log: level: info upstreams: - name: registry timeout: 5sHow your service reads it
Section titled “How your service reads it”Your service finds appConfig.data as a YAML file at /etc/<name>/config.yaml, and the environment variable
CONFIG_DIR points at the directory, /etc/<name>. <name> is metadata.name of the
Fairway manifest, not the Runway service ID. For the example
manifest, example-frontend, your service reads:
log: level: infoupstreams: - name: registry timeout: 5sRead the file from $CONFIG_DIR/config.yaml rather than hard-coding the path. In Go, LabKit’s
v2/config package loads and validates the file
against your configuration’s protobuf message:
loader, err := config.New()if err != nil { return err}
var cfg configv1.Configif err := loader.Load(filepath.Join(os.Getenv("CONFIG_DIR"), "config.yaml"), &cfg); err != nil { return err}LabKit doesn’t read CONFIG_DIR itself, so your service builds the path. The same protobuf message can also
validate appConfig
when Fairway builds the chart.
- Changes restart the pods. The chart adds a
checksum/configannotation to the pods, so a change toappConfigrolls out new pods. Your service only needs to read the file at startup. CONFIG_DIRis reserved. Don’t set it inenvironmentwhenappConfigis set: Fairway rejects the values. Likewise, don’t set the pod annotationchecksum/config.- CronJobs don’t get it. Only the pods of your service’s Deployment have the file, not the pods of
cronJobs.
The Fairway service owner guide
describes appConfig in detail, including how to validate it against your service’s schema.
Tenant-specific application config
Section titled “Tenant-specific application config”String values in appConfig.data can be templates, which Runway expands for each tenant. Use them for
values such as the URL of the tenant’s GitLab instance:
appConfig: data: gitlab: url: "https://{% .ManagedDomain %}"Runway expands the templates before Helm renders the chart, so config.yaml holds the expanded value, for example
url: https://example.gitlab-dedicated.com. A tenant can also override appConfig.data in the
Tenant Model, but use templates for anything that every tenant needs.
Templates
Section titled “Templates”Some values differ between tenants in a predictable way. For example, a service that validates GitLab OIDC tokens
needs the URL of the tenant’s GitLab instance. Instead of a Tenant Model override for each tenant, set the value to a
template in values-dedicated.yaml:
appConfig: data: auth: issuer: # Must equal the `issuer` in https://<gitlab-host>/.well-known/openid-configuration. url: "https://{% .ManagedDomain %}" keys_url: "https://{% .ManagedDomain %}/oauth/discovery/keys"environment: GITLAB_URL: "https://{% .ManagedDomain %}/"On a tenant whose managed_domain is example.gitlab-dedicated.com, the module is deployed with:
appConfig: data: auth: issuer: url: "https://example.gitlab-dedicated.com" keys_url: "https://example.gitlab-dedicated.com/oauth/discovery/keys"environment: GITLAB_URL: "https://example.gitlab-dedicated.com/"Templates are Go text/template templates. Runway expands them when it deploys
the module, after it has merged the Tenant Model overrides. The catalog entry stores the templates, not the expanded
values.
Where templates are expanded
Section titled “Where templates are expanded”| Field | Expanded |
|---|---|
appConfig.data |
Every string value, at any depth, including strings in lists. Keys are not expanded. |
environment |
Every value. Variable names are not expanded. |
Templates in any other field are not expanded, and are passed to the Helm chart as they are.
Template data
Section titled “Template data”| Field | Type | Description |
|---|---|---|
.ManagedDomain |
string | Domain of the tenant’s GitLab instance, for example example.gitlab-dedicated.com, from managed_domain in the Tenant Model. Never empty. |
Writing templates
Section titled “Writing templates”- Template delimiters are
{%and%}. Runway uses them to tell its own templates apart from Helm templates, which use{{and}}. Runway leaves anything in{{ }}, such as{{ .Release.Name }}, unchanged.{% .ChartVersion %}inchartRefuses the same delimiters. - Quote templated values in YAML. A value that starts with
{%, such as{% .ManagedDomain %}, is not a valid plain YAML scalar unless it is quoted. - Only strings can be templates. Numbers, booleans, and other non-string scalars can’t be templated. A template always expands to a string, even if the result looks like a number.
- Only the fields in Template data exist. An unknown field, such as
{% .Domain %}, fails the deployment, as does a template that doesn’t parse. The error names the value, for examplevalues.appConfig.data.auth.issuer.url. - Release-time checks see the template, not the expanded value. If your service validates
appConfigagainst a schema when the chart is built, the templated value must pass it as written, for example a field with a URL pattern must accepthttps://{% .ManagedDomain %}. - Strings without template actions are unchanged. There is no escaping to worry about unless a value contains
{%. To produce a literal{%, write{% "{%" %}.