Environment variables
Every runner env var with its default, effect, and owner.
Defaults below are what docker/compose.yaml applies when the var is unset. The runner refuses dev secrets off localhost. CELLD_* vars are passed through to tenant fleets — see celld.dev for their semantics, not here.
Ownership: runner-owned (read by the runner) vs worker-passthrough (handed to the control UI worker untouched).
Domain and edge
| Var | Default | Effect | Owner |
|---|---|---|---|
BASE_DOMAIN |
localhost |
Tenant + control DNS base | runner-owned |
CONTROL_SUBDOMAIN |
(empty) | Empty = control on bare domain; prod sets app |
runner-owned |
CONTROL_EXTRA_HOSTS |
(empty) | Comma-separated extra control hostnames | runner-owned |
CADDY_AUTO_HTTPS |
unset = on except localhost |
off behind a terminating proxy |
runner-owned |
HTTP_PORT |
9080 |
Host HTTP port | runner-owned |
HTTPS_PORT |
9443 |
Host HTTPS port | runner-owned |
GIT_PUBLIC_BASE |
http://git.localhost:9080 / https://git.<domain> |
Public Git remote base | runner-owned |
BETTER_AUTH_URL |
http://localhost:9080 |
Control origin; set before first registration (passkeys bind to it) | worker-passthrough |
CADDYFILE_PATH |
/data/caddy/Caddyfile |
Generated Caddyfile location | runner-owned |
CADDY_ACCESS_LOG |
/data/caddy/access.log |
JSON access log tailed for device/path/ref stats | runner-owned |
CADDY_ADMIN_URL |
http://127.0.0.1:2019 |
Caddy admin endpoint for /load writes |
runner-owned |
RUNNER_BIND |
0.0.0.0:8080 |
Runner listen address | runner-owned |
RUNNER_WORK_DIR |
/data/runner |
Fleet working dirs, mirrors, builds | runner-owned |
RUNNER_POLL_MS |
5000 |
Reconcile tick interval | runner-owned |
CELLD_BIN |
celld |
Fleet binary path | runner-owned |
CONTROL_BUNDLE_DIR |
/opt/noite/control/dist |
Baked control UI bundle (fleet #0) | runner-owned |
RUST_LOG |
noite_runner=info,tower_http=info |
Runner log filter | runner-owned |
Auth
| Var | Default | Effect | Owner |
|---|---|---|---|
RUNNER_TOKEN |
dev-runner-token |
Bearer token for runner API + UI server calls; refused off-localhost at default | runner-owned |
BETTER_AUTH_SECRET |
dev-only-change-me-to-a-long-random-string |
Session signing secret; dev- values refused on real domains |
worker-passthrough |
NOITE_ADMIN_EMAIL |
(empty) | Account promoted to admin at startup | worker-passthrough |
NOITE_EMAIL_WEBHOOK_URL |
(empty) | Receives lost-passkey OTP JSON; unset = log on localhost, refuse on real domains | worker-passthrough |
NOITE_SMTP_FROM |
(empty) | OTP sender label | worker-passthrough |
Store
| Var | Default | Effect | Owner |
|---|---|---|---|
NOITE_S3_BUCKET |
noite |
Single bucket; git/, fleets/, control/ prefixes |
runner-owned |
S3_ENDPOINT |
http://rustfs:9000 |
Object-store endpoint | runner-owned |
S3_PUBLIC_ENDPOINT |
http://127.0.0.1:9000 |
Endpoint returned to host-side tools | runner-owned |
AWS_REGION |
us-east-1 |
Store region | runner-owned |
AWS_ACCESS_KEY_ID |
RUSTFS_ACCESS_KEY → noiteaccess |
Store key | runner-owned |
AWS_SECRET_ACCESS_KEY |
RUSTFS_SECRET_KEY → 32-char dev default |
Store secret | runner-owned |
RUSTFS_ACCESS_KEY |
noiteaccess |
Bundled RustFS key | runner-owned |
RUSTFS_SECRET_KEY |
32-char dev default | Bundled RustFS secret | runner-owned |
RUSTFS_API_PORT |
9000 |
Bundled RustFS S3 port (loopback) | runner-owned |
RUSTFS_CONSOLE_PORT |
9001 |
Bundled RustFS console port | runner-owned |
NOITE_DB |
./data/noite.sqlite |
Runner SQLite path (sqlite: prefix optional) |
runner-owned |
Tenancy
| Var | Default | Effect | Owner |
|---|---|---|---|
NOITE_TENANCY |
unset = multi off localhost, single on |
multi sandboxes builds/fleets as users behind egress policy; single = operator pushes only |
runner-owned |
RUNNER_BUILD_UID / RUNNER_BUILD_GID |
10010 |
Build sandbox user | runner-owned |
RUNNER_FLEET_UID / RUNNER_FLEET_GID |
10020 |
Tenant fleet user | runner-owned |
NOITE_FLEET_PORTS |
built-in range | Fleet port range, two ports per app | runner-owned |
NOITE_STOP_BUDGET_MS |
25000 |
Graceful-stop budget (min 5000); fleets stop inside budget − 3 s |
runner-owned |
RUNNER_BUILD_MAX_MB |
2048 |
Refuse builds past this worktree size (MiB) | runner-owned |
Bounds
| Var | Default | Effect | Owner |
|---|---|---|---|
RUNNER_MAX_APPS_PER_USER |
10 |
Per-account app quota, enforced on create | runner-owned |
RUNNER_FLEET_MAX_RSS_MB |
0 |
→ CELLD_MAX_RSS_MB when non-zero; container-wide shed threshold, not per-app; 0 = celld default (80% of container memory) |
worker-passthrough (fleet) |
RUNNER_FLEET_IDLE_EVICT_S |
120 |
→ CELLD_IDLE_EVICT_S; idle seconds before a fleet hibernates cells |
worker-passthrough (fleet) |
RUNNER_FLEET_DEPLOY_POLL_S |
300 |
→ CELLD_DEPLOY_POLL_S; covers missed /reload only |
worker-passthrough (fleet) |
RUNNER_FLEET_ASSET_CACHE_MB |
64 |
→ CELLD_ASSET_CACHE_BYTES; per-fleet on-disk asset cache |
worker-passthrough (fleet) |
RUNNER_OTEL_FLUSH_MS |
30000 |
→ CELLD_OTEL_FLUSH_MS; bucket-flush + ingest tick cadence (min 1000); dashboards lag ~40 s |
worker-passthrough (fleet) |
RUNNER_BUILD_CACHE_MB |
512 |
Per-app persistent bun cache cap; 0 disables |
runner-owned |
RUNNER_TELEMETRY_RETENTION_DAYS |
14 |
Days kept in bucket prune, metric tables, glob floor (min 1) |
runner-owned |
RUNNER_TIP_SWEEP_S |
60 |
Fallback S3 tip sweep per app; covers out-of-band bundle writes | runner-owned |
RUNNER_FLEET_LOG |
error,celld=warn |
Fleet RUST_LOG (runner’s own filter stays separate) |
worker-passthrough (fleet) |
Sleep
| Var | Default | Effect | Owner |
|---|---|---|---|
RUNNER_SLEEP_AFTER_H |
24 |
Park apps idle this long; 0 = never |
runner-owned |
RUNNER_SLEEP_SWEEP_S |
3600 |
Sleep sweep cadence | runner-owned |
RUNNER_WAKE_TIMEOUT_S |
120 |
Longest a held request waits for a woken fleet; failed wake answers 503 |
runner-owned |
See Limits for the sleep mechanism.
Telemetry and rate limits
| Var | Default | Effect | Owner |
|---|---|---|---|
NOITE_RATE_LIMIT_RPM |
600 |
Worker per-route-class (auth/invite) limit; 429 + Retry-After; 0 disables |
worker-passthrough |
NOITE_AUTH_RATE_LIMIT |
600 |
better-auth per-client /api/auth/* budget; 0 disables |
worker-passthrough |
Platform-only
Set by the platform, not by operators. Railway: PORT (listen port), RAILWAY_DEPLOYMENT_ID, RAILWAY_DEPLOYMENT_DRAINING_SECONDS (must exceed the stop budget + 5 s). TINI_SUBREAPER is set by the image’s init. None of these go in .env.