Skip to main content

Configuration overview

ERun's configuration lives in three layers. Each layer holds different kinds of settings and is consulted at different points in the lifecycle.

Three configuration layer cards side by side. PER-USER (cyan-stroked) at ~/.config/erun/, edited by you / erun init / desktop, read every command, holds ERunConfig · TenantConfig · EnvConfig. PER-PROJECT (cyan-stroked) at <repo>/.erun/config.yaml, edited by team in PRs, read every build/push/deploy, holds ProjectConfig. PER-POD ENV VARS (charcoal) set by helm at deploy, derived automatically, read by erun in the runtime pod, examples ERUN_TENANT and ERUN_NAMESPACE.
At deploy time the helm chart derives the per-pod environment variables from the per-user and per-project layers.

For exact file paths see Config locations. For the in-pod env var list see Environment variables. For how erun build resolves project root / context / version, see Build path resolution.


Per-user config

ERunConfig (~/.config/erun/config.yaml)

Global defaults that apply across all tenants.

FieldTypeUsed byEffect
default_tenantstringerun (no args), erun open, erun listTenant used when no tenant argument is supplied.
cloudproviders[]listerun init, erun open, erun cloudKnown cloud provider identities (alias, provider, account, profile, SSO/OIDC settings). provider is aws or cloudflare. AWS entries carry the SSO profile and OIDC issuer; Cloudflare entries carry an account ID and a token reference into the local secret store (the scoped API token itself is never stored in this file). Each cloud-bound env references entries by alias.
cloudcontexts[]listerun open, erun deploy, erun cloudKnown managed cloud clusters. Each binds a cluster to a provider, region, and instance type/size.
runtimeregistry.namespacestringerun version, runtime image pullsOverrides where ERun looks for erun-devops and related runtime images. Useful for internal mirrors (Harbor, ECR, Artifactory).
runtimeregistry.repositorystringsame as aboveOverrides the repository name in the namespace.
runtimeregistry.baseurlstringsame as aboveRegistry HTTP endpoint. Defaults differ for Docker Hub vs GHCR.
runtimeregistry.tokenurlstringsame as aboveGHCR token endpoint. Only used on the GHCR flow.

TenantConfig (~/.config/erun/<tenant>/tenant.yaml)

One per tenant.

FieldTypeUsed byEffect
namestringAll commandsTenant identifier.
defaultenvironmentstringerun (no args), erun open, erun listEnvironment used when none is supplied.
api_urlstringerun open (API port forward)Backend API base URL for this tenant.
cloudprovideraliases[]list of stringserun init, erun openCloud provider aliases the tenant is allowed to use.
primarycloudprovideraliasstringerun open (suggesting cloud bindings)Default cloud provider alias for new envs in this tenant.

EnvConfig (~/.config/erun/<tenant>/<env>/config.yaml)

One per environment. This is the most-edited file.

FieldTypeUsed byEffect
namestringAll commandsEnvironment identifier.
typestring (enum)erun build, erun open, erun deploy, helm chart (worktreeStorage, ERUN_ENV_TYPE)local-agent, remote-agent, or runtime. The canonical — and only — signal for what this env is for. Configs written before type existed are migrated to a concrete value on read (see Legacy migration). See Environment types.
localRepoPathstringhelm chart (worktreeHostPath for local-agent envs), erun deploy (project-config load, registry resolution)Absolute path to the repo on the local machine. Only meaningful for local-agent envs; left empty for remote-agent and runtime.
repopathstring(legacy, read-only)Removed struct field. A repopath: in an older config is migrated into localRepoPath on read and dropped on the next save; new configs only carry localRepoPath. (Migration done.)
kubernetescontextstringerun open, erun deploy, erun listKubernetes context to deploy/open against. Special value in-cluster is set inside the runtime pod.
containerregistrieslisterun build, erun push, erun deploy, build image tag resolutionPer-env marked registry list, set for remote-agent/runtime envs whose project config is not on the local machine (local-agent envs resolve the list from the project's .erun/config.yaml instead). Each entry is {registry, roles} where roles ⊆ build/from/to/deploy. See Container registries.
cloudprovideraliasstringerun open, erun deploy, idle-stop, audit labelsWhich AWS cloud provider identity backs this env. Resolves to a cloudproviders[] entry.
cloudprovideraliasesmap (provider → alias)erun open, erun deployAdditional cloud aliases attached to this env, keyed by provider type (e.g. cloudflare). AWS stays in the scalar cloudprovideralias above for backward compatibility; non-AWS providers live here. An env holds at most one alias per provider type, so a runtime pod can receive both an AWS identity and a Cloudflare token at once. A Cloudflare alias injects CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID for in-pod tooling.
managedcloudboolhelm chart (ERUN_CLOUD_ENVIRONMENT)When true, marks the env as running on a managed cloud context (enables idle-stop, cloud-credential refresh, etc.).
runtimeversionstringerun open, erun deploy, chart appVersionPins the version of the runtime image used by this env.
runtimeregistrystringerun open, erun deployPer-env override for where the environment resolves ERun's own artifacts: it is the first registry the runtime chart search probes, and it is projected into the pod as RUNTIME_REGISTRY for in-pod platform image resolution. Set it with erun init --runtime-registry <host> when the env's deploy-marked registry holds only your project's own images — ERun publishes charts/erun-devops beside the runtime image it releases, never beside your application images. A successful deploy or open records the registry the runtime chart actually resolved from here, so the next search starts there — but only to fill the field in or confirm it: a value you set is never replaced by a deploy that resolved somewhere else, which is traced instead.
runtimeimagestringerun open, erun deployPoints the env's runtime pod at a custom image instead of the published <registry>/erun-devops:<version> default. A full reference (ghcr.io/acme/acme-runtime:1.2.3) is used verbatim; a bare name resolves to <registry>/<name>:<runtime version>. Set by erun init --runtime-image; carried to the published chart as imageOverrides.erun-devops on every deploy (see Advanced chart values). Optional for a tenant that publishes its own charts/<tenant>-devops umbrella — that deploy defaults the image to the umbrella's own <tenant>-devops image, so set runtimeimage only to pin a different image; an image-only env riding the shared charts/erun-devops chart still needs it. A value that resolves to the stock erun-devops image is ignored on an umbrella deploy (see Advanced chart values).
runtimechartstringerun deployNames the runtime chart this env rides, as an OCI reference that may carry its own version (oci://ghcr.io/sophium/charts/erun-devops:1.0.178). Set it when the runtime image is versioned on the project’s own release line rather than ERun’s: the chart is ERun’s artifact and exists only at ERun’s versions, so deriving both from runtimeversion names a chart that was never published and the deploy fails FetchReference … not found. With the chart stated here, runtimeversion keeps stamping the env and tagging the image, and each artifact is named on the line it ships on — see erun deploy · naming the chart and the image separately. Omit the :<version> suffix to name only a different registry, keeping the paired version. Empty (the default) keeps the published lookup at the runtime version, which is right whenever erun push published chart and image together. Overridable for one run with erun deploy --runtime-chart, which is not persisted. Editable in the desktop app under an environment’s Runtime tab (Runtime chart).
mcpauthpublickeypathstringerun deploy, erun init, erun openPath to the PEM public key the env's erun-mcp edge verifies bearer tokens against — the desktop file:// path. Recorded by a deploy that enabled authentication (erun deploy --mcp-auth-public-key <path>, erun init --mcp-auth-public-key <path>) at the moment it applies the key to the cluster rather than after the rollout completes — so a rollout that fails afterwards still leaves the env naming the key its release trusts — and rethreaded by every later deploy of the runtime chart, so a plain version bump keeps the edge authenticated instead of falling back to the chart default. Cleared only by erun deploy --no-mcp-auth, once that unauthenticated release has rolled out. Empty means the env never had desktop MCP authentication. See erun deploy · MCP edge authentication is sticky.
imagepullsecretslist of stringserun deploy, helm chartNames Kubernetes dockerconfigjson secrets the runtime pod authenticates image pulls with — needed when the runtime image is a private registry package (e.g. a private <tenant>-devops umbrella image; ERun's own erun-devops/erun-dind images are public, so a default env needs none). Threaded to the chart as imagePullSecrets[i].name, re-scoped under the erun-devops subchart key for an umbrella. Empty (the default) leaves the pod pulling anonymously, so public-image envs are unaffected. Each named secret is re-minted from a host-resolved credential on every erun deploy when one is available (ECR via the AWS CLI, anything else via your local docker session) — see Advanced chart values.
mountsourceboolerun deploy (runtime worktree storage + clone wiring), helm chartRuntime-only opt-in for real-time patching: when true (together with repourl), the runtime pod gets a PVC-backed worktree it clones from repourl at the deployed release tag on first boot. A no-op without repourl, and ignored for agent envs (which already carry source). Default false keeps a runtime env sourceless (deploy-by-reference). Set from the desktop's Runtime tab → Mount source code. See Environment types → Hotfix pattern.
repourlstringerun deploy, helm chart (ERUN_REPO_URL)Git remote the runtime pod clones when mountsource is set. Cloned into the in-pod worktree and checked out at v<runtimeversion> (the deployed release tag).
autoupgradeboolerun upgrade, desktop Upgrade allWhen true, this env joins the Upgrade-all set: erun upgrade redeploys it to the latest version for its channel when runtimeversion lags.
disablebuildscriptboolerun build, erun build --deployWhen true, erun ignores any project build.sh for this env and resolves docker/release builds directly; if there is no docker build context either, the build errors with no buildable context. Default false.
platformaccountboolerun deploy, helm chartWhen true, designates the env's runtime ServiceAccount as the cluster's erun platform admin: deploy threads --set platformAccount=true and the runtime chart binds the SA to the built-in cluster-admin ClusterRole (a <release>-platform ClusterRoleBinding). This is the grant that lets in-pod erun terraform apply of the cluster edge and platform component installs (cert-manager, Traefik, PowerDNS) create the cluster-scoped resources they require — namespaces, CRDs, cluster RBAC, webhooks. Default false leaves the SA with namespaced admin only. Set with erun init --platform-account. The first deploy that creates the binding must run from an admin-capable context — the API server's privilege-escalation check requires the creator already hold cluster-admin, so an in-pod self-deploy cannot bootstrap it.
upgradechannelstring (enum)erun upgradeRelease channel an upgrade targets: stable (semver releases) or snapshot (latest snapshot build, or the stable release once one is published on top of it — see erun upgrade). Orthogonal to type. When unset, defaults from type — runtime → stable, agent → snapshot.
stoppedboolerun stop, erun open, erun deploy, helm chart (stopped)Records that the Operator stopped this environment. erun stop sets it and scales the runtime Deployment to zero; erun open clears it and scales back up. deploy renders the chart's stopped value from it, so the chart emits replicas: 0 and a helm upgrade reconciles the stop instead of silently restarting the pod — a bare scale patch alone would be drift the next upgrade reverts. deploy therefore never wakes an environment; opening it does. Clearing the field is an Operator gesture: erun open --reconnect, the form a supervisor uses to re-establish a dropped session, leaves it set (see erun open), so a stop is not erased by the reconnects the stop itself triggers. Default false.
runtimepod.cpustringhelm chart (runtime.resources.limits.cpu)CPU limit for the runtime pod (e.g. 4, 500m).
runtimepod.memorystringhelm chart (runtime.resources.limits.memory)Memory limit (e.g. 8916Mi, 2Gi).
sshd.enabledboolerun open, chart (SSH port-forward setup)Whether the in-pod SSH server is exposed via port forward. Needed for IDE attach.
sshd.localportinterun openLocal port the desktop binds for the SSH forward. 0 = auto-allocate.
sshd.publickeypathstringerun open --vscode, erun open --intellij, SSH key syncPath to the SSH public key authorized for the env.
sshd.workspacesync.enabledbooldesktop workspace-sync pollerMirror a local folder into the runtime workspace.
sshd.workspacesync.localpathstringdesktop workspace-sync pollerThe local folder to mirror.
deploy.timeoutduration (e.g. 5m0s)erun deploy, erun upgrade (helm --timeout)Per-env helm rollout wait. How long deploy waits for the rollout to become ready before helm times out; the pod monitor keeps waiting up to this bound while an image is still pulling and aborts earlier on a real failure. Unset → 5m0s. Overridden per-deploy by --rollout-timeout / the MCP deploy timeout input. A malformed value fails the deploy.
deploy.componentslisterun deployPer-machine saved deploy selection: the charts erun deploy rolls out for this env by default (chart directory names under <tenant>-devops/k8s/, plus the runtime release name <tenant>-devops). Set it with erun init --components <a,b,…>, or from the desktop app's Runtime-tab checklist (inside the Version-to-deploy picker, gated until you pick a version, "Set as default"). It is a published-version view, the same for every env type: once you pick a version it offers the component charts actually published at that version (plus the runtime) — the version, not the env's local source, decides which charts exist, so a version that never published a chart doesn't list it, and a local-agent env shows the same published components as a runtime env rather than its local working-tree chart directories. (Deploying local working-tree charts by name stays available to an operator via the CLI.) Deploy is opt-in: --components overrides this per run; when both are empty, deploy falls back to the project's k8s.deployments plan, then to the runtime chart alone. See selection precedence. Empty → no saved selection. Because a saved selection wins over the plan permanently and tiers never merge, a deploy sourced from the saved set traces deploy: saved components shadow the repo plan; plan also names <a, b, …> whenever the plan names something the saved set omits — and erun init --components '' is the way back to the plan, clearing the saved selection outright rather than leaving it stuck.
idle.timeoutduration (e.g. 5m0s)chart (ERUN_IDLE_TIMEOUT), in-pod idle monitorHow long the env must be quiet before idle-stop fires.
idle.workinghoursstring (HH:MM-HH:MM)chart (ERUN_IDLE_WORKING_HOURS), idle monitorWindow during which idle-stop is allowed to fire.
idle.timezonestringchart (ERUN_IDLE_TIMEZONE), idle monitorTime zone for workinghours.
idle.idletrafficbytesint64chart (ERUN_IDLE_TRAFFIC_BYTES), idle monitorBytes/window below which the env is considered network-quiet.
claude.usemantle*boolchart (CLAUDE_CODE_USE_MANTLE)Route Claude through Mantle.
claude.usebedrock*boolchart (CLAUDE_CODE_USE_BEDROCK)Route Claude through AWS Bedrock.
claude.models[]listchart (ERUN_CLAUDE_AVAILABLE_MODELS)Allow-list of Claude models for in-pod tools.
claude.maxoutputtokens*intchart (CLAUDE_CODE_MAX_OUTPUT_TOKENS)Max output tokens per Claude response.
claude.effort*stringdesktop AI launcher (claude --effort / claude --settings)Effort level for the env's Claude AI tab, one of low, medium, high, xhigh, max, ultracode. Unset or invalid → ultracode. The five --effort levels launch as claude --effort <level>; ultracode is not an --effort value — it launches as claude --settings '{"ultracode":true}' and enables xhigh effort plus standing multi-agent workflow orchestration. Only the default Claude launch is affected; a non-claude aitool or a Claude launch the Operator wrote with explicit flags is left untouched. Saving a change from the desktop reopens the env's open AI tabs; the session resumes via --continue.
claude.defaultmodel*stringdesktop AI launcher (claude --model)Model the env's Claude AI tab starts on. Applied while it is one of the env's available models (claude.models[], or the default available set when that list is empty); when unset — or set to a model no longer in that set — the launch falls back to the first available model (opus by default) rather than passing no --model, so a managed session never defers to Claude Code's own default model (Fable), which erun's pod auth does not serve. Model names are opaque tokens to ERun — resolving one (e.g. fable) to a concrete model is Claude's concern. fable stays strictly opt-in: it is never in the default available set and launches only when the Operator both lists it under claude.models[] and selects it here. The resolved model is also mirrored into CLAUDE_CODE_SUBAGENT_MODEL on the launch, so subagents spawned inside that session run on the env's model instead of Claude Code's separate subagent default; it is left unset only when no available model is a usable token. Same verbatim-launch carve-out and save-reopen behaviour as claude.effort.
claude.verbosedebugbooldesktop AI launcher (claude --verbose --debug)Launch the env's Claude AI tab with Claude's own verbose + debug diagnostics streaming into the tab. Absent means off. Same verbatim-launch carve-out and save-reopen behaviour as claude.effort.
aitoolstringdesktop AI launcher, runtime entrypointWhich Agent is the default for this env (claude, codex, …).
localportrangestartintdesktop port allocatorBase port for this env's local forwards (MCP, API, SSH).
autostart*booldesktop sidebar opennil = ask, true = always start linked cloud context on open, false = never.
remotehostcredentialsbool— (deprecated no-op)Deprecated. Host AWS credential delivery now follows AWS-alias attachment: attaching an AWS cloud alias to an env delivers its credentials into the runtime pod (the alias association is the opt-in — "act on my behalf here"), so no separate toggle is needed. Retained only so existing configs still parse; setting it has no effect.

The four claude.* rows above (usemantle, usebedrock, models[], maxoutputtokens) are the Claude values erun manages itself. The runtime chart accepts further claude.* values that erun never sets — pin a model, point Bedrock at a VPC endpoint, tune prompt caching — via the env's values overlay; see Advanced chart values.

The env's Claude AI tab also launches with Remote Control enabled by default: the managed launch appends --remote-control <tenant>/<env> after the effort/model/verbose flags, so the session is drivable from the Claude iOS app under that name. It is omitted when claude.usemantle or claude.usebedrock is set — Remote Control pairs through the claude.ai account relay, which those gateway auth modes can't authenticate — and follows the same verbatim-launch carve-out as claude.effort (a non-claude aitool, or a Claude launch written with explicit flags, is left untouched). An environment name that isn't a plain token (alphanumerics, ., -, _, starting with an alphanumeric) falls back to the unnamed --remote-control, leaving Claude Code to name the session from the pod hostname. For the Operator view, see Desktop app.


Per-project config

ProjectConfig (<repo>/.erun/config.yaml)

Committed to the repo, applies to anyone who checks it out.

FieldTypeUsed byEffect
containerregistrieslisterun build, erun push, erun deploy, build image tag resolutionProject-wide marked registry list. Each entry is {registry, roles} with roles ⊆ build/from/to/deploy. See Container registries.
environmentsmapper-env settings (below)Map of <env-name> → ProjectEnvironmentConfig.
environments.<env>.containerregistrieslisterun build, erun push, erun deployPer-env marked registry list override. Higher precedence than the top-level project list.
environments.<env>.docker.fingerprintsmaperun build, erun build --releasePer-image content fingerprints from the last published build. Drives the fingerprint cache.
environments.<env>.k8s.deployments[]ordered listerun deployThe ordered deploy plan for this env. Each step is either a single component name or a list of names deployed in parallel.
release.mainbranchstringerun releaseMain branch name (default main).
release.developbranchstringerun releaseDevelop branch name (default develop).
platformmaperun deploy (PowerDNS), erun exposePer-instance platform deployment config. Absent for non-platform projects. See platform: block.
pathsmaperun build, erun push, erun deploy, erun terraformOverrides where erun discovers the devops assets — the docker/ and k8s/ folders, the terraform-<tenant> root, and the VERSION file. Absent → the conventional layout. See paths: block.

paths: block

The paths: block relocates the devops assets erun otherwise discovers by convention: the docker/ build contexts and k8s/ Helm charts under <tenant>-devops/, the per-environment Terraform roots at terraform-<tenant>/ (or <tenant>-devops/terraform-<tenant>/), and the VERSION file. It is project-global (not per-environment). Reach for it when a repo does not nest these under a <tenant>-devops/ module — for example a devops repo whose root carries docker/, k8s/, terraform-<tenant>/, and VERSION as top-level siblings.

Every field is optional; an unset field keeps the conventional location. A configured path resolves relative to the project root (an absolute path is used as-is). The override relocates the canonical folders, it does not rename them: the docker/ and k8s/ directories keep those exact names (erun's build and deploy machinery keys off the folder name), so a configured docker/k8s path must end in a docker/k8s segment. One field, dockercontext, is not a path but a mode selector for how the Docker build context is chosen.

FieldTypeDefaultEffect
dockerstring<tenant>-devops/dockerDirectory (named docker) whose subdirectories are the per-component build contexts (<docker>/<component>/Dockerfile). Read by erun build / erun push.
dockercontextrepo-root | componentpositional heuristicSelects the Docker build context root, overriding the positional heuristic (see Build context directory). repo-root → context is the project root (so a Dockerfile can COPY from anywhere in the repo); component → context is the component build dir. Unset keeps the heuristic. Read by erun build / erun push / erun release.
k8sstring<tenant>-devops/k8sDirectory (named k8s) whose subdirectories are the per-component Helm charts (<k8s>/<component>/Chart.yaml). Read by erun deploy, and by erun build for chart packaging.
terraformstringterraform-<tenant> or <tenant>-devops/terraform-<tenant>Base directory under which the per-environment Terraform roots live; erun still appends /<environment>. Read by erun terraform, which by convention checks terraform-<tenant>/ then <tenant>-devops/terraform-<tenant>/ (the same -devops discovery as docker/k8s) before this override is needed.
versionstringwalk up from the build dir to the project rootPath to the VERSION file that mints the build version. A directory resolves to <dir>/VERSION. Read by erun build / erun push / erun release.
# <repo>/.erun/config.yaml — a devops repo that holds the folders at its root
paths:
docker: docker
k8s: k8s
terraform: terraform-frs
version: VERSION

Error behaviour. A configured override that does not resolve fails the command (exit code 1) rather than silently falling back to convention:

  • paths.docker / paths.k8s pointing at a directory that is missing, not named docker/k8s, or holding no build contexts / charts → configured docker path "<p>" (.erun/config.yaml paths.docker) is not a docker build module: … (and the k8s analogue).
  • paths.terraform with no <base>/<environment>/ directory → no Terraform root at <dir> … the .erun/config.yaml paths.terraform base "<p>" must contain a <env>/ dir ….
  • paths.version pointing at a missing file → configured version file <p> (.erun/config.yaml paths.version) not found.
  • paths.dockercontext set to anything other than repo-root or componentinvalid docker context "<v>" (.erun/config.yaml paths.dockercontext): expected "repo-root" or "component".

platform: block

The platform: block configures an erunpaas platform deployment — an installation that runs the global singletons (the PowerDNS nameserver, the hosted IdP) and exposes tenant services under a delegated zone. ERun's platform is generic, installable software: any vendor deploys it under their own names, so every value is configuration — nothing (not even the base domain) is hardcoded. erun deploy threads these as platform.* helm values (the erun-powerdns and erun-zitadel charts read them); erun expose reads them to resolve service hostnames.

The whole block is optional. An empty block means the project runs no platform deployment. Once any field is set the block is "in use" and is validated at deploy/expose time — a malformed block fails fast.

FieldTypeRequiredEffect / default
basedomainstringyes (when block in use)The registered domain this deployment serves, e.g. erunpaas.com. Everything else derives from it. Must be a valid domain name.
envstringnoThe dedicated platform environment that owns the singletons (PowerDNS, the DNS-01 broker), e.g. frs-prod. Must be a DNS-safe <tenant>-<env> namespace label.
serviceszonestringnoThe child zone delegated to this deployment's PowerDNS, under which tenant services are exposed. Default services.<basedomain>. Must be a valid domain at or under basedomain.
authoritativeipstringnoThe public IP this deployment's authoritative nameserver answers on (the glue-record target for serviceszone). Must parse as an IP when set.
nameserverslistnoThe NS hostnames the parent zone delegates serviceszone to. Default [ns1.<basedomain>, ns2.<basedomain>].
authhoststringnoThe hosted-IdP host, served from the apex zone (not serviceszone). Default auth.<basedomain>. Must be a valid domain at or under basedomain. The erun-zitadel chart issues tokens for this origin, and a platform deploy adds https://<authhost> to the API's ERUN_OIDC_ALLOWED_ISSUERS so the control plane trusts its own IdP.
acmeemailstringnoThe account email for this deployment's Let's Encrypt registration (LE rate limits are per registered domain, so each deployment uses its own account).
caaissuerstringnoCA domain the services zone authorizes via apex CAA records (issue + issuewild), e.g. letsencrypt.org. Empty (default) writes no CAA — any CA may issue. Opt-in because it must match the CA the cluster edge's ACME server uses; a mismatched CAA blocks issuance. When set, the erun-powerdns zone-bootstrap also gives per-env empty-non-terminal names a definitive CAA answer instead of an ambiguous NODATA.
apiurlstringnoThis deployment's own API base URL, e.g. https://api.frs-prod.services.erunpaas.com. Served unauthenticated at GET /v1/platform so a client can discover it; an unset value renders as an empty string, never an error.
consoleurlstringnoThis deployment's hosted web console URL. Same discovery contract as apiurl.
brandstringnoThis deployment's display name, if set. Same discovery contract as apiurl.
# <repo>/.erun/config.yaml
platform:
basedomain: erunpaas.com
env: frs-prod
authoritativeip: 203.0.113.10
caaissuer: letsencrypt.org # optional; authorizes only this CA on the zone
apiurl: https://api.frs-prod.services.erunpaas.com # optional; served at GET /v1/platform
consoleurl: https://console.frs-prod.services.erunpaas.com # optional; served at GET /v1/platform
# serviceszone, authhost, and nameservers default from basedomain:
# serviceszone: services.erunpaas.com
# authhost: auth.erunpaas.com
# nameservers: [ns1.erunpaas.com, ns2.erunpaas.com]

A second vendor installs the same artifacts under their own names — e.g. basedomain: kppaas.com, env: kp-prod — with no code changes.


Per-pod env vars

The helm chart writes these into the runtime pod at deploy time. They're derived from the per-user and per-project layers above; you don't edit them directly. Full list at Environment variables.


Advanced chart values (Operator escape hatches)

The runtime chart accepts more values than erun manages. At deploy time erun passes two layers to helm upgrade --install:

  1. The env's values overlay — values.<env>.yaml in the runtime chart directory (<tenant>-devops/k8s/<tenant>-devops/values.<env>.yaml). It is passed with -f and is required: deploy aborts with values file not found for environment "<env>" when it is missing. Environments that deploy the published erun-devops chart have no local chart directory; for them the overlay lives next to the env's config at <UserConfigDir>/erun/<tenant>/<environment>/values.yaml (e.g. ~/.config/erun/<tenant>/<environment>/values.yaml on Linux) and is optional — when absent, the chart defaults plus erun's --set list fully describe the deploy.
  2. erun's own --set/--set-string list, derived from EnvConfig and the resolved plan.

Helm gives --set precedence over -f, so for every key erun manages the overlay can never win. The keys below are exactly the ones erun's --set list never includes — for them the values.<env>.yaml overlay is authoritative, which makes it the supported escape hatch for behaviour erun doesn't model.

claude.* model and Bedrock tuning

Each value renders as an env var on the runtime container, and the pod's entrypoint relays it into the Agent's ~/.claude/settings.json. Both steps are AWS-gated: the chart renders this env block only when the env's cloud provider is aws (cloudContext.provider), and the entrypoint relay runs only when Bedrock configuration is active — an AWS provider, or CLAUDE_CODE_USE_BEDROCK / CLAUDE_CODE_USE_MANTLE set, with a resolvable region.

Chart valueEnv varDefaultEffect
claude.modelANTHROPIC_MODELunsetPin the primary model Claude uses.
claude.defaultOpusModelANTHROPIC_DEFAULT_OPUS_MODELunsetPin the model ID the opus alias resolves to (Bedrock model IDs, anthropic.-prefixed).
claude.defaultSonnetModelANTHROPIC_DEFAULT_SONNET_MODELunsetPin the model ID the sonnet alias resolves to.
claude.defaultHaikuModelANTHROPIC_DEFAULT_HAIKU_MODELunsetPin the model ID the haiku alias resolves to.
claude.bedrockBaseURLANTHROPIC_BEDROCK_BASE_URLunsetRoute Bedrock traffic through a VPC endpoint or gateway instead of the public endpoint.
claude.mantleBaseURLANTHROPIC_BEDROCK_MANTLE_BASE_URLunsetBase URL for the Bedrock Mantle gateway.
claude.bedrockServiceTierANTHROPIC_BEDROCK_SERVICE_TIERunsetBedrock service tier: default, flex, or priority.
claude.skipMantleAuthCLAUDE_CODE_SKIP_MANTLE_AUTHunsetSkip Mantle's own auth step (set 1 when the gateway handles auth).
claude.disablePromptCachingDISABLE_PROMPT_CACHINGunsetTurn prompt caching off (set 1).
claude.enablePromptCaching1HENABLE_PROMPT_CACHING_1HunsetUse the 1-hour prompt-cache TTL on Bedrock (set 1).
claude.maxThinkingTokensMAX_THINKING_TOKENS1024Thinking-token budget per response.
claude.smallFastModelAWSRegionANTHROPIC_SMALL_FAST_MODEL_AWS_REGIONcloudContext.regionAWS region for the small/fast helper model, when it differs from the env's region. Like AWS_REGION, the env var is emitted only when a region resolves — see Environment variables.

The claude.* keys erun does manage — claude.useBedrock, claude.useMantle, claude.availableModels, claude.maxOutputTokens — come from the EnvConfig fields above and are always --set; an overlay value for them is ignored.

Runtime pod resource requests

erun manages only the runtime pod's resource limits: EnvConfig.runtimepod.cpu / .memory (set with erun init --runtime-cpu / --runtime-memory or the desktop's env settings) are always --set as runtime.resources.limits.{cpu,memory}. The requests are overlay-only:

Chart valueDefaultEffect
runtime.resources.requests.cpu0.25CPU request for the runtime pod.
runtime.resources.requests.memory1024MiMemory request for the runtime pod.

Request overrides are invisible to erun open's redeploy drift detection — it compares the deployed limits against EnvConfig.runtimepod and ignores requests — so changing a request in the overlay takes effect on the next deploy, not automatically on the next open.

Runtime image override

imageOverrides.erun-devops is a supported public value of the runtime chart: it replaces the image the erun-devops container runs while keeping the rest of the chart canonical. The supported way to set it is the EnvConfig.runtimeimage field (erun init --runtime-image), which erun passes as --set-string imageOverrides.erun-devops=<image> on every deploy of the published chart — the intended path for custom toolchain images built FROM the published erun-devops image (the erun-build-env skill walks through it).

When runtimeimage is unset, erun defaults the override from the chart it is deploying: a deploy of the tenant's own published charts/<tenant>-devops umbrella defaults imageOverrides.erun-devops to that umbrella's own image, <registry>/<tenant>-devops:<version>. erun push publishes the umbrella and its <tenant>-devops image together at one version, so the chart's identity names the image — building and pushing it is enough for the deploy to run it, with no runtimeimage to set and no silent fall-back to a stock erun-devops:<version> the tenant's version line never published. A deploy of the shared charts/erun-devops chart carries no such signal: with runtimeimage unset it passes no override (the chart's own default image runs, and the overlay may set the value directly), so an image-only build env still points at its image through runtimeimage.

An explicit runtimeimage normally wins over that default — the operator's choice. Two cases are treated as stale leftovers rather than a deliberate current choice, and are ignored in favour of the default above:

  • The shared-chart-to-umbrella migration: on a <tenant>-devops umbrella deploy, a runtimeimage that resolves to the stock erun-devops image (any registry) is a leftover from when the env rode the shared chart — the tenant's version line never publishes erun-devops, so honouring it would pin a tag that 404s (ImagePullBackOff). Trace: deploy: ignoring stale runtimeimage <image> on the <tenant>-devops umbrella deploy ….
  • A runtimeimage that names the very same image this deploy's own line would already resolve unaided (the umbrella's own image, above), just at some other tag: that pin is provably redundant, so its tag can only be a stale one left behind by an earlier deploy, and the current version's tag is used instead. Trace: deploy: ignoring stale runtimeimage <image> (this deploy's own line already publishes <default-image>; the saved pin just names an older tag); defaulting to <default-image>.

A non-stock runtimeimage naming a genuinely different image (a real custom image, or a hotfix build) still wins as before, and the shared charts/erun-devops chart still honours a stock erun-devops value (there it is correct). Because both stale cases exist, erun init --runtime-image records the value tagless — the deploy path above already pins a tagless reference to the env's own runtime version, so recording a tag is what creates the rot in the first place.

Private image pull secrets

The runtime pod pulls its image anonymously by default, which is correct for ERun's public erun-devops/erun-dind images. When the runtime image is a private registry package — most commonly a private <tenant>-devops umbrella image — the pod needs a pull credential. Set EnvConfig.imagepullsecrets (via erun init --image-pull-secret) to the name(s) of the dockerconfigjson secret(s) the pod should pull with; erun deploy threads them to the chart as imagePullSecrets[i].name (re-scoped under the erun-devops subchart key for an umbrella), and the runtime pod authenticates its pulls with them. Leaving the list empty threads nothing, so public-image envs are byte-for-byte unchanged.

Before every rollout, erun deploy also re-mints each named secret's content from a credential it resolves for the deploy registry on whichever machine runs the deploy — the same resolution version listing uses: the AWS CLI for an ECR host, otherwise your local docker session. This is what keeps an ECR-hosted runtime image pullable past its authorization token's twelve-hour expiry without an operator noticing the rot and recreating the secret by hand. When no credential resolves (no AWS CLI or docker session available where deploy runs), the named secret is left exactly as it was — the first deploy to name a pull secret still needs it to already exist unless erun can resolve a credential for it itself.

Turning the MCP edge off

The runtime container serves the environment's MCP edge on mcpPort, which is why an MCP tool call runs with the environment's own toolchain (see Inside an environment). The chart value mcpEnabled gates it and defaults to true; set mcpEnabled: false in the env's values overlay to run the pod with no edge at all — no listener, no advertised mcp container port. Everything else about the pod is unchanged, and an Agent or desktop client then has no MCP endpoint for that env.

Runtime pod shape extensions

The runtime chart exposes five additive, no-op-by-default extension points for build environments that need pod shape the image override cannot express — a sidecar, an extra volume/mount, extra env, or the cluster RBAC a sidecar needs:

ValueMerges into
extraContainersthe pod's containers list (sidecars)
extraVolumesthe pod's volumes list
extraVolumeMountsthe erun-devops container's volumeMounts
extraEnvthe erun-devops container's env
extraRulesan extra ClusterRole (<release>-extra) bound to the runtime ServiceAccount, for cluster-scoped RBAC a sidecar needs; namespaced access already comes from the built-in admin binding

Set them through the env's values overlay. Deployed as the published chart directly, they sit at the top level. When a <tenant>-devops umbrella wraps the published chart as a subchart (the erun-build-env skill Step 6), nest them under the erun-devops key — and erun deploy re-scopes every runtime value it sets (tenant, ports, cloud context, MCP auth, and the imageOverrides.erun-devops image) under that same erun-devops. subchart key, so the wrapped runtime is wired exactly as the published chart would be. A plain (non-wrapped) runtime keeps top-level values.

An example overlay:

# <tenant>-devops/k8s/<tenant>-devops/values.prod.yaml
claude:
model: anthropic.claude-opus-4-8
bedrockServiceTier: priority
runtime:
resources:
requests:
cpu: "1"
memory: 2048Mi

Resolution order

Some values can be set in multiple layers. When that happens, ERun consults them in a fixed order.

Container registries

A project declares a list of registries, each marked with the roles it plays. The list resolves per environment, then individual registries are selected by role.

Resolving the list for an environment:

  1. EnvConfig.containerregistries (per-user, per-env — set for remote-agent/runtime envs).
  2. ProjectConfig.environments.<env>.containerregistries (per-project, per-env override).
  3. ProjectConfig.containerregistries (per-project, top-level).
  4. Built-in default seed: a single ghcr.io/sophium entry marked build + deploy.

Roles (each entry carries any subset):

RoleMeaningCount
builderun build/erun push push target; the <registry> of the build image tag.≤ 1
fromCopy source on deploy.≤ 1
toCopy destination(s) on deploy.≥ 0
deployRegistry the cluster pulls from (rendered as containerRegistry in the chart).≥ 1 (required)

Role rules (validated when the list is resolved for build or deploy):

  • At most one build, at most one from; at least one deploy.
  • from and to are set together and must name different registries.
  • More than one deploy → the first wins.

A deploy registry need not also carry build or to: the image it serves may be published there externally (e.g. a runtime env that pulls a released image), which erun does not police at config time.

Behaviour:

  • Build pushes to the build registry. No build registry → the environment cannot build (erun build aborts: environment "<env>" has no build registry; exit code 1).
  • Deploy copies each image the cluster needs (the runtime image and any locally-built component) from from to every to with docker buildx imagetools create (manifest-aware), then the cluster pulls from the deploy registry. The copy runs only when both from and to are set.

Migration: a legacy single containerregistry: X scalar (project or env config) is read once as a one-entry list [{registry: X, roles: [build, deploy]}] and rewritten in the list shape on the next save.

Deploy chart source

erun deploy installs charts by reference from the published registry — the runtime chart (oci://<registry>/charts/erun-devops + imageOverrides.erun-devops) and each selected platform component (oci://<registry>/charts/erun-<component>), threading tenant/environment and the env's config as top-level --set. A runtime env needs no local source: its worktree is none, and components deploy by reference (release-named <tenant>-<component>, in default-rank order), so the deploy runs from anywhere — the operator's machine or the control plane. When the env's repo is local (an agent env, or an in-pod checkout for real-time patching) and carries a chart for a selected component, that local chart is used instead — the optional patch path.

Component charts are published per release (each erun-<component> chart rides erun push), but only from the release each was added — so which charts a version offers depends on the version. The desktop's Components checklist reflects this: it probes the registry for the charts published at the selected deploy version (charts/erun-<component>, distinct from the runtime image tags the version picker lists) and offers only those. A version that never published a component's chart doesn't list it; selecting one anyway (via --components) fails the deploy with a chart-not-found error.

Kubernetes context

  1. EnvConfig.kubernetescontext (per-env explicit).
  2. For local envs only: kubectl config current-context if EnvConfig.kubernetescontext is empty.
  3. Inside the runtime pod: always in-cluster.

Effective tenant + environment for a CLI command

  1. Explicit --tenant / --environment flags.
  2. Current working directory, matched against each tenant's environments' localRepoPath (longest match wins; an ambiguous tie across tenants resolves to no match).
  3. ERunConfig.default_tenant and that tenant's defaultenvironment.
  4. Interactive prompt (TTY only).
  5. Error otherwise.

Runtime version

  1. EnvConfig.runtimeversion (explicit pin).
  2. The CLI's own built-in build version (the erun version value).

For Docker build context / version resolution, see Build path resolution.


How to inspect your effective config

What you wantWhere to find it
Default tenant + env, every tenant's envs, current effective targeterun list
The resolved registry/context/runtime version for an enverun list (per-env block)
The exact config inside a running runtime podMCP list tool, or erun list inside the pod
Open the per-env settings UIDesktop app → click the env in the sidebar → edit modal
Audit who/what last changed a valueThe audit trail (CLI audit: lines) and git history of <repo>/.erun/config.yaml

Migration and planned changes

ERun has moved from the legacy remote + snapshot field pair to one explicit EnvConfig.type. type is the single signal for the env's shape — its worktree storage and chart wiring:

  • EnvConfig.type is written by erun init --type, the desktop env settings, or by editing the YAML directly.
  • ✅ The helm chart wiring (worktreeStorage=host|pvc|none, ERUN_ENV_TYPE) branches on type. The delivery commands (erun build, erun push, erun deploy, erun open) are pure primitives and do not branch on typebuild mints a version, push publishes it, deploy installs it by reference, open opens a shell. The caller (the desktop app, or an Operator) decides which primitives to run for a given env.
  • EnvConfig.localRepoPath is the local-host project path the env was created against. erun init seeds it for every env type (#549) — it is the single source for cwd→tenant matching, the erun open repo path, and the deploy worktree repo name. For local-agent envs it is also the hostPath mounted into the pod; remote-agent / runtime envs use a PVC worktree, so the value names the in-pod worktree (by its basename) but is never mounted. For local-agent envs it can be retargeted after creation from the desktop's env settings (General tab → Repository path, with a native Browse picker); the change applies on Save and takes effect on the next deploy. For remote-agent / runtime envs the field stays read-only there — their repo is not a local host path.
  • EnvConfig.remote, EnvConfig.snapshot, and the matching TenantConfig fields are removed. A config written before type existed is migrated on read: ERun parses the legacy remote/snapshot keys, derives type per the table below, and discards them. No action is needed — re-saving an env (e.g. from the desktop) persists the resolved type.
  • EnvConfig.repopath is removed — migrated into localRepoPath on read, dropped on the next save.
  • TenantConfig.projectroot is removed (see Field-level moves). Working-directory→tenant matching now compares the cwd against each tenant's environments' localRepoPath; a config written with projectroot still loads — the key is ignored on read and dropped on the next save.

Legacy remote/snapshottype migration

A config with no type is migrated from the retired keys on read. snapshot absent is read the same as snapshot: false:

remotesnapshotMigrated typeWorktree storageBuild behaviour
falsetruelocal-agenthostPath mount of localRepoPathsnapshot tags, builds in-pod
truetrueremote-agentPVC checkout cloned from gitsnapshot tags, builds in-pod
truefalse / absentruntimenonerelease tags only, no builds
falsefalse / absent(unresolved)hostPath (fallback)

See Environment types for what each value means in practice.

Field-level moves

FieldStatusWhy
EnvConfig.remote✅ RemovedSubsumed by type (local-agent ↔ false; remote-agent / runtime ↔ true). Legacy YAML migrated on read.
EnvConfig.snapshot✅ RemovedSubsumed by type (local-agent / remote-agent ↔ true; runtime ↔ false). Legacy YAML migrated on read.
TenantConfig.remote / TenantConfig.snapshot✅ RemovedBelonged on the env, not the tenant; subsumed by per-env type.
TenantConfig.projectroot✅ Removed; cwd→tenant matching iterates over envs' localRepoPath (longest match wins; ambiguous ties resolve to no match). Legacy YAML ignored on read and dropped on save.A tenant can host both local and remote envs; the path lives on the env, not the tenant.
EnvConfig.repopath✅ Removed; migrated into EnvConfig.localRepoPath on read and dropped on save.Scoped explicitly: the local-machine path mounted into the pod. For PVC envs the field is unset — the repo lives inside the pod at a fixed convention.