# Environment variables

URL: https://helmr.dev/docs/reference/env
Description: Supported environment configuration for Helmr services and the CLI.

# Environment variables

Helmr names environment variables according to the environment that owns them:

- A dedicated Helmr process uses direct configuration names such as
  `DATABASE_URL`, `CONTROL_PLANE_ADDR`, and `WORKER_RESOURCE_ID`. Repeating
  `HELMR_` inside a process-specific environment adds no ownership information.
- `HELMR_` is reserved for public client configuration and values that cross
  into an environment Helmr does not exclusively own, such as a user's shell
  or a Computer guest. The CLI variables below are examples.
- Established ecosystem variables such as `AWS_*`, `HTTP_PROXY`, and
  `NO_PROXY` keep their standard names where supported by the owning tool.
  Platform integration clients use the explicit `OUTBOUND_PROXY_URL` contract below.

Environment variable names are an exact, case-sensitive contract. Helmr does
not read legacy aliases. Text, numeric, boolean, and duration settings ignore
surrounding whitespace. Passwords and tokens are opaque and are read exactly as
provided. Base64 root keys must be canonical without surrounding whitespace.
This page covers supported operator and client configuration. Variables owned
entirely by the guest image, tests, or development tooling remain internal.

## CLI

| Variable | Purpose |
| --- | --- |
| `HELMR_API_URL` | Control-plane base URL. |
| `HELMR_API_KEY` | Bearer token for CLI or `HelmrClient`. |
| `HELMR_PROJECT` | Project ID for Environment and `env` commands when `--project` is absent. With an API key, it must match the key's Project. |
| `HELMR_ENV` | Environment ID for Environment commands when `--env` is absent. With an API key, it must match the key's Environment. |
| `HELMR_CONFIG_DIR` | CLI state directory override. |

Role grant and member permission commands ignore both variables: there, omitting `--project` or `--env` means organization-wide or unfiltered, so an ambient default would change a grant's scope.

`HelmrClient` does not read these variables automatically. Pass the URL and API
key from your application's configuration to its constructor.

## Control Plane

Required: `DATABASE_URL`, `CAS_URI`, `CLICKHOUSE_URL`,
`DEPLOYMENT_RUNTIME_DESCRIPTOR_PATH`, `PLATFORM_STORE_URI`,
`WORKER_HOST_CREDENTIAL_SIGNING_KEY`, `AUTH_KEY`, `ENCRYPTION_KEY`,
`COMPUTER_FENCING_KEY`. Configure `GITHUB_OAUTH_CLIENT_ID` and
`GITHUB_OAUTH_CLIENT_SECRET` together to enable GitHub login; both may be omitted.

Deployment uploads require the same explicit process configuration in Managed
Cloud and self-hosted installations. There are no server-side capacity defaults:

| Variable | Purpose |
| --- | --- |
| `DEPLOYMENT_UPLOAD_PROCESS_CONCURRENCY` | Positive maximum active object uploads, including preflight, in one Control Plane process. |
| `DEPLOYMENT_UPLOAD_ORGANIZATION_CONCURRENCY` | Positive maximum for one organization in that process, no greater than the process maximum. Set them equal when one organization may use all capacity. |
| `DEPLOYMENT_UPLOAD_IDLE_TIMEOUT` | Maximum interval without body progress, including blocked storage; a duration such as `2m`, at least `1ms`. |
| `DEPLOYMENT_UPLOAD_MAX_DURATION` | Absolute duration of one admitted body transfer, at least the idle timeout; a duration such as `2h`. |
| `CONTROL_PLANE_HTTP_IDLE_TIMEOUT` | HTTP keepalive idle time **between requests**, distinct from upload progress; defaults to `2m`. Set it longer than the reverse proxy's pooled-connection idle time. |

Upload capacity is per process, not a cluster-wide quota or reserved tenant share.
When full, the server returns a retryable 429 with `Retry-After`. Organization
caps do not guarantee fairness or prevent one actor using multiple organizations.
Preflight is bounded to 30 seconds. After authorization, descriptor validation
and storage preflight, transfers release ordinary API concurrency but retain
upload capacity until transfer work and input cleanup complete. CPU, memory,
database and network resources are still shared; measure headroom under ordinary
traffic when choosing capacity.

The server duration bounds one admitted transfer. The CLI deadline includes
capacity waiting and transfer, so allow retry headroom rather than setting both
deadlines equal. A shorter caller, ingress or storage limit can terminate an
operation first. Coordinate upload inactivity limits with ingress timeouts;
HTTP keepalive is a separate setting. Runtime examples are starting points for
measurement, not production capacity recommendations.

Environment execution limits must also be configured explicitly, on both the control
plane and the dispatcher, with the same values. They have no implicit defaults. Set
positive integer values appropriate for your deployment. The values apply to every
Environment and take effect when the processes restart; a lowered admission burst
applies at the next admission.

| Variable | Purpose |
| --- | --- |
| `ENVIRONMENT_MAX_RESIDENT_COMPUTERS` | Maximum Computers holding an execution allocation at once. |
| `ENVIRONMENT_MAX_CPU_MILLIS` | Bound on physical whole-vCPU reservations across the Environment, in millicores. A fractional request rounds up: `cpu: 0.5` reserves 1000 millicores. A non-multiple of 1000 leaves its remainder unusable. A Computer that cannot fit on its own is rejected at creation. |
| `ENVIRONMENT_MAX_MEMORY_BYTES` | Bound on memory reservations across the Environment. A Computer larger than this bound is rejected at creation. |
| `ENVIRONMENT_MAX_RESERVED_STORAGE_BYTES` | Bound on reserved logical Computer storage. Each Computer reserves 32 GiB; a lower bound rejects fresh Computer creation. |
| `ENVIRONMENT_MAX_OUTSTANDING_ADMISSIONS` | Maximum queued and running Turns. |
| `ENVIRONMENT_ADMISSION_RATE_PER_SECOND` | Refill rate for new Turn admission tokens. |
| `ENVIRONMENT_ADMISSION_BURST` | Maximum admission tokens; one new Turn consumes one token. |

Starting execution beyond the aggregate CPU, memory or resident Computer bound is
refused; queued work that cannot fit waits and reports the Environment limit as its
reason. Retrying an accepted request does not consume another token. Disk
reservations are distinct from physical CPU/memory allocation and from measured
stored bytes. These resource and admission bounds do not promise a monetary spend
ceiling.

Requester ancestry for created Sessions is limited to a depth of 8. Computer
preparation, image builds and the start of execution each have a fixed 10-minute
deadline.

Diagnostic ingestion requires explicit positive integer limits. The dispatcher also uses
these limits for diagnostic export and requires the export batch settings below:

| Variable | Purpose |
| --- | --- |
| `DIAGNOSTIC_CHUNK_BYTES` | Maximum bytes in one log data record, at most 16 MiB. |
| `DIAGNOSTIC_SOURCE_BYTES`, `DIAGNOSTIC_SOURCE_RECORDS` | Pending byte and record ceilings for one producer. |
| `DIAGNOSTIC_ENVIRONMENT_BYTES`, `DIAGNOSTIC_ENVIRONMENT_RECORDS` | Pending ceilings across an Environment. |
| `DIAGNOSTIC_QUEUE_BYTES`, `DIAGNOSTIC_QUEUE_RECORDS` | Pending ceilings across the diagnostic queue. |
| `DIAGNOSTIC_DB_MAX_CONNECTIONS` | Maximum connections in each process’s dedicated diagnostic pool, additional to its ordinary database pool. |
| `DIAGNOSTIC_EXPORT_BATCH_RECORDS` | Dispatcher: maximum records per diagnostic export batch. |
| `DIAGNOSTIC_EXPORT_BATCH_BYTES` | Dispatcher: maximum data bytes per diagnostic export batch, at least `DIAGNOSTIC_CHUNK_BYTES`. |

Byte ceilings must satisfy chunk ≤ source ≤ Environment ≤ queue. Record ceilings
must satisfy source ≤ Environment ≤ queue. Gap and end records count toward the
record limits. These ceilings do not reserve capacity for a producer or Environment.
A busy or full queue asks producers to retry; diagnostic pool exhaustion leaves
the ordinary pool available for lifecycle requests.

Missing, invalid or inconsistent diagnostic or deployment-upload settings prevent
the relevant service from starting. Before a rollout, load each service's intended environment
and run its binary with `check-config`:

```sh
control-plane check-config
dispatcher check-config
```

These commands use the same environment loaders as service startup and exit nonzero
on invalid settings. They do not start services, connect to dependencies, read the
Runtime descriptor, or check credentials against external services. A successful
check does not prove full startup or availability. Runtime sink failures and queue
exhaustion continue to use bounded retry and backpressure; separate pools do not
isolate a shared PostgreSQL outage. Accepted records retain their configured expiry.

Deployment mode: `DEPLOYMENT_MODE` defaults to `self-hosted`. First-organization setup
requires a deployment operator session and a separately authenticated ordinary account.
Managed-cloud organization creation follows the configured creation policy.
See [authentication](/docs/self-hosting/authentication) for operator enrollment.

Regions and Worker Groups are PostgreSQL resources managed through the Admin
API and Console. Control Plane startup does not reconcile them from process
configuration, and a deployment may start with neither resource.

An optional startup bootstrap seeds one Region and one execution Worker Group.
Set `BOOTSTRAP_ENABLED=true`. When the named Worker
Group does not exist, `BOOTSTRAP_WORKER_TOKEN` is also required.
`BOOTSTRAP_REGION_ID` and
`BOOTSTRAP_WORKER_GROUP_NAME` default to `default`.
`BOOTSTRAP_REGION_DISPLAY_NAME` and `BOOTSTRAP_REGION_LOCATION` are optional.
Bootstrap creates missing resources and never updates an existing Region or
Worker Group. Changing either bootstrap identity can therefore create another
missing seed; it does not rename or delete an existing resource. When needed,
the token must use the `hlmr_wgt_` format; only its SHA-256 hash is stored in
PostgreSQL.

`BOOTSTRAP_REGION_ID` is an opaque Helmr identifier, not a provider Region or
DNS name. Its normalized UTF-8 value must be 1–255 bytes and contain no
surrounding whitespace or control characters.

Deployment operators use separate, passkey-authenticated identities. Ordinary user
email addresses never confer deployment authority. Issue enrollment through the
local deployment command described in the authentication guide.

Optional: `CONTROL_PLANE_ADDR` and `APP_ORIGIN`.
`APP_ORIGIN` is the canonical browser/API origin used for login callbacks, Console
links and Slack setup. It can be private HTTPS if users' browsers can reach it.

`SLACK_TRANSPORT` selects `http` (default), `socket` or `disabled` for both Control
Plane and Dispatcher. It is deployment-wide and is read-only in Console. Socket
Mode receives events through outbound WSS; HTTP mounts per-registration event
and interaction callbacks. Disabled rejects setup and starts no Slack delivery
workers. Dedicated app credentials are organization-managed encrypted database
records, not process environment variables.

`OUTBOUND_PROXY_URL` is an optional absolute HTTP(S) proxy origin for platform
GitHub OAuth, Slack OAuth/API/Socket and Resend traffic. An empty value means
explicit direct access. There is no automatic direct fallback or `NO_PROXY`
bypass. The proxy must support CONNECT to HTTPS/WSS targets. These clients do
not inherit ambient `HTTP_PROXY` or `HTTPS_PROXY`. PostgreSQL, configured
ClickHouse, S3/KMS and internal readiness/control-plane clients use direct service
paths; this setting is not a guest or worker-build egress policy. In the private
reference, ClickHouse must have a private route and narrowly scoped security
rules. See [private deployments](/docs/self-hosting/private).

`CAPACITY_TOKEN` enables and authenticates the capacity API used by trusted
deployment automation, including an operator-supplied scaler. It must be
canonical unpadded base64url that decodes to exactly 32 bytes. The scaler sends
the same value as a Bearer token over HTTPS outside localhost. Possession grants
the complete capacity API for the deployment, so store it as a secret and
provide it only to the Control Plane and trusted scaler. When it is unset,
capacity API requests are rejected. See [custom capacity
scaling](/docs/self-hosting/capacity) for setup and rotation.

ClickHouse telemetry: `CLICKHOUSE_URL` is required. Set `CLICKHOUSE_USER` when the service user is not `default`, and set `CLICKHOUSE_PASSWORD` when the service requires a password.

`AUTH_KEY`, `COMPUTER_FENCING_KEY`,
`ENCRYPTION_KEY`, and `WORKER_HOST_CREDENTIAL_SIGNING_KEY` are distinct single roots.
Each must be base64 and decode to exactly 32 bytes. Every Control Plane replica uses
the same values. Online rotation and multi-key verification are not supported.

Computer data keys have a separate Control Plane wrapping root:

- Self-hosted deployments require `COMPUTER_WRAPPING_KEY_ID` (a stable, nonempty
  identifier) and `COMPUTER_WRAPPING_KEY` (base64, exactly 32 bytes). All Control
  Plane replicas must use the same values. Preserve this key and identifier with
  database backups; replacing either makes existing Computer keys unreadable.
- Managed-cloud deployments require `COMPUTER_KMS_KEY_ARN`, the full ARN of a
  symmetric KMS key. The Control Plane uses native AWS credentials and the ARN's
  region. Its identity needs encrypt permission on that key and decrypt permission
  on every wrapping key still referenced by retained Computer keys. Retain those
  KMS keys; changing the configured ARN to another key in the same region only
  affects newly wrapped keys. Cross-region wrapping-key migration is not supported.
- Wrapping keys and provider credentials belong only to the Control Plane.
  Workers receive scoped data keys through authenticated runtime authority.
  Provider errors never fall back to local wrapping material.

Email delivery is disabled by default. Set `EMAIL_PROVIDER` to choose a sender:

| Provider | Required variables | Optional variables |
| --- | --- | --- |
| `none` | None. This is the default when no email settings are present. | None |
| `log` | `EMAIL_PROVIDER=log` | Sign-in codes and invitation links are written to the Control Plane log instead of being sent. For local development only; rejected when `DEPLOYMENT_MODE` is `managed-cloud`. |
| `smtp` | `EMAIL_PROVIDER=smtp`, `SMTP_ADDR`, `EMAIL_FROM` | `SMTP_USERNAME`, `SMTP_PASSWORD` |
| `resend` | `EMAIL_PROVIDER=resend`, `RESEND_API_KEY`, `EMAIL_FROM` | None |

The configured sender delivers sign-in links and member invitations. Without a sender, members can't be invited.

`EMAIL_FROM` must be an email address or display-name address accepted by the selected provider, such as `Helmr <noreply@example.com>`.

### Database bootstrap command

`control-plane database-bootstrap [reset]` uses a separate administrative
connection to create or reset the application database role. These variables
are command inputs; the long-running Control Plane does not read the
administrative credentials.

| Variable | Purpose |
| --- | --- |
| `DATABASE_ADMIN_HOST` | Required administrative PostgreSQL host. |
| `DATABASE_ADMIN_PORT` | Administrative PostgreSQL port. Defaults to `5432`. |
| `DATABASE_ADMIN_USERNAME` | Required administrative role. It must differ from the application role in `DATABASE_URL`. |
| `DATABASE_ADMIN_PASSWORD` | Required administrative role password. |
| `DATABASE_NAME` | Required database name. `DATABASE_URL` must target the same name. |
| `DATABASE_URL` | Required application connection URL. It must target the administrative endpoint with TLS, and include the application role and password. |

## Dispatcher

Required: `DATABASE_URL`, `CLICKHOUSE_URL`, `COMPUTER_FENCING_KEY`,
`ENCRYPTION_KEY`, and `CONTROL_PLANE_URL`.

`CONTROL_PLANE_URL` must be the same reachable Control Plane endpoint used by
Workers. Each Dispatcher probes its `/readyz` endpoint and requires 120 seconds
of uninterrupted healthy samples before fencing stale Worker observations.
Failures, missed samples and Dispatcher restarts reset this recovery window.

The dispatcher uses the same `ENCRYPTION_KEY` as the Control Plane to encrypt
CA signers when it creates protected Computers for scheduled Agent admissions.

The dispatcher uses the same single base64-encoded 32-byte
`COMPUTER_FENCING_KEY` as the Control Plane service.

## Worker

Worker log buffering requires `WORKER_LOG_CHUNK_BYTES`, `WORKER_LOG_BUFFER_BYTES`
and `WORKER_LOG_BUFFER_RECORDS` as positive integers. The chunk size must fit the
16 MiB transport limit and the control plane's `DIAGNOSTIC_CHUNK_BYTES`; the buffer
byte limit must be at least one chunk. Record count must fit a positive int32.
These are per-producer memory limits, not durable queue reservations.


Required for every Worker: `CONTROL_PLANE_URL`, `CAS_URI`,
`PLATFORM_STORE_URI`, `WORKER_RESOURCE_ID`,
`WORKER_POOL_NAME`, `WORKER_ENROLLMENT_TOKEN_FILE`,
`CHECKPOINT_ENCRYPTION_KEY`, `JAILER_UID`, `JAILER_GID`,
`WORKER_NETWORK_LINK_POOL`, `WORKER_NETWORK_TRANSLATION_POOL`,
`WORKER_NETWORK_RESOLVER_IPV4`, and
`WORKER_NETWORK_BLOCKED_IPV4_CIDRS`. The blocked-prefix value is a canonical,
ordered JSON array; use `[]` only when the deployment intentionally supplies no
additional blocked destinations. Built-in reserved-address, metadata, host and
peer restrictions still apply.

Network installation settings are shared between the Control Plane and Workers:

| Variable | Default | Purpose |
| --- | --- | --- |
| `CONTROL_PLANE_NETWORK_PRIVATE_BUSINESS_CIDRS` | `[]` | Canonical IPv4 RFC1918 prefixes eligible for authored private network grants. |
| `CONTROL_PLANE_NETWORK_BLOCKED_IPV4_CIDRS` | `[]` | Additional management or privileged prefixes denied by every eligible Worker. |
| `WORKER_NETWORK_PRIVATE_BUSINESS_CIDRS` | `[]` | The Worker's approved private business ranges. Must be contained in the Control Plane ceiling. |
| `WORKER_NETWORK_BLOCKED_IPV4_CIDRS` | Required | The Worker's additional denied ranges. Must cover the Control Plane's denied ranges. |

All four values are canonical, ordered JSON arrays of IPv4 CIDRs. Private business
ranges must not overlap denied ranges. An empty private ceiling denies RFC1918
egress even when a Template omits `network`. For example, permitting
`["10.42.0.0/16"]` requires matching installation configuration and actual private
routing/DNS; a Template domain grant additionally needs an `ipRanges` bound within
that ceiling. An infrastructure grant alone does not add a Template domain grant.

A Worker may use a stricter ceiling or broader denies. The Control Plane rejects
allocation delivery and renewal when the Worker is more permissive or cannot
report the network enforcement contract. Update and qualify Workers before
admitting policies that depend on this contract, and drain older execution before
changing installation restrictions. Existing Computers retain their creation
Template's policy; a Template redeployment does not edit it.

The Worker reads its Worker Group enrollment token from the strict-permission
token file and presents it as a Bearer credential over TLS. The token selects
the Worker Group; the Worker does not configure a group ID. Control Plane
validates the Pool enrollment against that group, records token use, creates
the authoritative Worker-instance identity, and issues a per-instance host
secret stored at `WORKER_HOST_SECRET_PATH`. The Worker exchanges that host
secret for short-lived host credentials that authenticate its requests.
`WORKER_RESOURCE_ID` remains an opaque deployment-owned locator for the
physical Worker. Provider identity and infrastructure inventory are deployment
responsibilities rather than Control Plane authentication inputs.

Enrollment returns a `worker_resource_custody` conflict while any prior Host for
the same Group and resource locator retains an unfenced VM allocation, including
private preparation VMs. A `lost` status alone does not prove physical cleanup.
The prior Host remains visible to the capacity provider until its physical absence
is confirmed; registration cannot hide it behind a new Host identity.

`WORKER_WORK_DIR` and `WORKER_IMAGES_DIR` select Worker state and image
directories. The following advanced settings connect the Worker to binaries
and host layout supplied by its deployment image:

| Variable | Default | Purpose |
| --- | --- | --- |
| `FIRECRACKER_PATH` | `firecracker` | Firecracker binary. |
| `CPU_TEMPLATE_HELPER_PATH` | `cpu-template-helper` | Firecracker CPU-template helper binary. |
| `JAILER_PATH` | `jailer` | Firecracker jailer binary. |
| `MKFS_EXT4_PATH` | `/usr/local/libexec/helmr/mkfs.ext4` | ext4 formatter used for VM disks. |
| `MKE2FS_CONFIG_PATH` | `/usr/share/helmr/mke2fs.conf` | Configuration passed to the ext4 formatter. |
| `IP_PATH` | `ip` | `iproute2` binary used for VM networking. |
| `NFT_PATH` | `nft` | nftables binary used for VM networking. |
| `JAILER_CHROOT_DIR` | Derived from the Worker VM state directory. | Base directory for jailer chroots. |
| `JAILER_CGROUP_VERSION` | `2` | Cgroup version passed to the jailer. |

VM sizing, Worker capacity, disk budgeting, and concurrency use these settings:

| Variable | Default | Purpose |
| --- | --- | --- |
| `VM_VCPUS` | `2` | vCPUs assigned to each VM. |
| `VM_MEMORY_MIB` | `2048` | Memory assigned to each VM. |
| `VM_SCRATCH_DISK_MIB` | `8192` | Scratch disk assigned to each VM. |
| `WORKER_CAPACITY_VCPUS` | `VM_VCPUS` | Total vCPU capacity advertised by the Worker. It cannot be smaller than `VM_VCPUS`. |
| `WORKER_CAPACITY_MEMORY_MIB` | `VM_MEMORY_MIB` | Total memory capacity advertised by the Worker. It cannot be smaller than `VM_MEMORY_MIB`. |
| `WORKER_DISK_MIB` | Total capacity of the Worker filesystem. | Overrides the physical capacity used for disk budgeting. |
| `WORKER_DISK_RESERVE_MIB` | `1024` | Host disk space subtracted to form the physical disk budget. |
| `WORKER_EXECUTION_SLOTS` | `1` | Maximum concurrent executions admitted by the Worker. |
| `VM_INIT_TIMEOUT` | `30s` | Timeout for Firecracker SDK initialization. |
| `VM_HEALTH_TIMEOUT` | `30s` | Timeout for guest health convergence. |

When both cache limits are `0`, the Worker derives one shared cache budget and
splits it two-thirds to the substrate cache and one-third to the artifact
cache. When only one limit is `0`, that cache receives its independently
derived budget. The Worker advertises aggregate guest disk capacity after
subtracting both the reserve and cache budgets from the configured or measured
filesystem total.

The routed-network link and translation pools, resolver, and blocked CIDRs are
required inputs listed above. The AWS Worker profile sets
`VM_HEALTH_TIMEOUT=300s` for first-boot convergence on EC2; that is a provider
profile value, not the Worker process default.
