Skip to content

Configuration reference

VariableRequiredMeaning
ORCH_DOMAINyesPublic host name (example: orch.selfhostr.io)
HTTP_PORT, HTTPS_PORTnoHost ports for Caddy (default 80 and 443). The only ports published
ORCH_BASE_URLnoPublic URL, default https://$ORCH_DOMAIN. Set only for a non-standard port
ORCH_TLS_MODE, ORCH_BIND_ADDR, HTTPS_PUBLISH, ORCH_PUBLIC_URLnoCloudflare Tunnel mode, see installation
ORCH_TRUST_PROXYnoPeers allowed to set X-Forwarded-* (default: private networks, i.e. Caddy)
ORCH_VERSIONnoImage tag of orch-server and orch-web (default latest)
POSTGRES_PASSWORDyesDatabase password (POSTGRES_USER and POSTGRES_DB default to orch)
ORCH_ENCRYPTION_KEYyes32 random bytes, base64. Encrypts stored GitHub user tokens. Not in backups: store it separately (Backups and restore)
GITHUB_APP_ID, GITHUB_APP_SLUG, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRETyesFrom your GitHub App (Creating the GitHub App)
ORCH_ADMIN_GITHUB_LOGINSyes (*)Admins: GitHub logins, comma-separated, case-insensitive. They manage the Access page
ORCH_ALLOWED_GITHUB_LOGINS, ORCH_ALLOWED_GITHUB_ORGSno (*)Who may also sign in: GitHub logins and/or organizations, comma-separated
ORCH_ALLOW_ANY_GITHUB_USERnotrue lets any GitHub account sign in. Only for a server that is not public
ORCH_ALLOWLIST_CACHE_SECnoHow often org membership is re-checked for open sessions, default 300
ORCH_REPO_ACCESS_TTL_SECnoHow long a user’s role on a repository is cached, default 300 (see Security model)
ORCH_REPO_ACCESS_GRACE_SECnoGitHub outage grace for private repositories, default 900 (see Security model)
ORCH_ACCESS_GRACE_SECnoGitHub outage grace for org-based access, default 900 (see Security model)
GITHUB_WEBHOOK_SECRETyesSame value as the App’s webhook secret
GITHUB_APP_PRIVATE_KEY_FILEyesHost path of the App private key (default ./secrets/github-app.pem)
LOG_LEVELnoinfo by default
BACKUP_SCHEDULEnoBackup cron expression, UTC (default 0 3 * * *). Replaces BACKUP_HOUR_UTC
BACKUP_KEEP_DAILY, BACKUP_KEEP_WEEKLY, BACKUP_KEEP_MONTHLYnoRotation: days / ISO weeks / months kept (defaults 7, 4, 6). BACKUP_RETENTION_DAYS still sets the daily count
BACKUP_ON_STARTnotrue also takes a backup when the container starts
BACKUP_VOLUMEnoNamed volume (default backups) or host directory
BACKUP_AGE_RECIPIENTnoage public key(s): dumps are encrypted to .dump.age. Keep the private key off-server
BACKUP_S3_BUCKET, BACKUP_S3_PREFIX, BACKUP_S3_ENDPOINT, BACKUP_S3_REGION, BACKUP_S3_PROVIDERnoOff-site upload target (S3-compatible), see Backups and restore
BACKUP_S3_ACCESS_KEY_ID, BACKUP_S3_SECRET_ACCESS_KEYwith bucketCredentials of the bucket (never logged)
BACKUP_S3_PRUNEnotrue also applies the rotation in the bucket (default: never delete remotely)
POSTGRES_MAJORnoPostgres major for the database and the backup image (default 16)
VariableDefaultMeaning
SMTP_HOST(unset)SMTP server. Without it the e-mail channel is unavailable (Notifications)
SMTP_PORT587SMTP port
SMTP_SECUREfalsetrue: TLS from the first byte (port 465). false: STARTTLS when offered
SMTP_USER(unset)SMTP login. No authentication when unset
SMTP_PASSWORD(unset)SMTP password
SMTP_FROMorch <orch@domain>Sender address; the default uses the host of the public URL
VariableDefaultMeaning
VAPID_PUBLIC_KEY(unset)VAPID public key. Without the three variables the browser channel is unavailable
VAPID_PRIVATE_KEY(unset)VAPID private key. Keep it secret, and keep it: changing it unsubscribes every browser
VAPID_SUBJECT(unset)Contact for push services: mailto:[email protected] or an https:// URL (Notifications)

Generate the pair once with npx web-push generate-vapid-keys.

(*) In production the server refuses to start when nobody restricts sign-in: no ORCH_ADMIN_GITHUB_LOGINS, no ORCH_ALLOWED_GITHUB_LOGINS, no ORCH_ALLOWED_GITHUB_ORGS and no access approved on the Access page, unless ORCH_ALLOW_ANY_GITHUB_USER=true. Without an allowlist, anyone with a GitHub account could sign in, see the projects set to “All orch users on this server” and join their pools.

The usual setup is to list yourself in ORCH_ADMIN_GITHUB_LOGINS and let people in from the web UI (see “Managing access” in Server installation). A user may sign in if they are an admin, or their login is listed, or they are an active member of a listed organization, or an admin approved them on the Access page.

Org membership is checked at login with the user’s own GitHub token, and again for open sessions every ORCH_ALLOWLIST_CACHE_SEC seconds (default 300). Removing someone from the list or the org therefore takes effect within minutes (logins are re-checked on every request). Org checks need the GitHub App organization permission Members: Read-only (Creating the GitHub App) and the App installed on, or approved by, the organization. In development and tests an empty allowlist only logs a warning.

The server limits requests with @fastify/rate-limit. The auth and agent-connect counters live in Postgres and hold for the whole deployment when several server instances run; the other tiers are counted in each instance’s memory (they protect one process from load) and reset when it restarts, see Running several server instances. Each limit counts requests per window (RATE_LIMIT_WINDOW_SEC, default 60 s). Over the limit the server answers 429 with a Retry-After header, RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers, and the usual error body {"error": {"code": "rate_limited", "message": "..."}}. The web app shows a short notice.

VariableDefaultApplies to (key)
RATE_LIMIT_ENABLEDtruefalse turns the HTTP limiter off (also the agent message cap)
RATE_LIMIT_WINDOW_SEC60Window length of every limit below
RATE_LIMIT_API_MAX300All other routes (signed-in user, else client IP)
RATE_LIMIT_AUTH_MAX60Sign-in start and callback, access requests, OAuth authorize/register/token/revoke, agent enrollment, invite links (client IP)
RATE_LIMIT_WEBHOOK_MAX600POST /api/github/webhook (client IP). Bodies are capped at 1 MiB
RATE_LIMIT_MCP_MAX120/mcp (bearer token, else client IP)
RATE_LIMIT_HEAVY_MAX60Expensive reads such as the pool view, usage and task history (signed-in user)
RATE_LIMIT_STREAM_MAX30Live-log streams (SSE) opened per user. Counts openings, so a reconnect storm is limited; an open stream costs one hit
RATE_LIMIT_AGENT_CONNECT_MAX30New agent WebSocket connections (client IP)
RATE_LIMIT_AGENT_MSG_PER_SEC100Messages per second on one agent connection. Above it the socket is closed with code 4429; the agent reconnects with its normal backoff plus 10 s. 0 disables the cap

MCP is limited per bearer token and, to stop token spraying, also per client IP (5x the per-token limit). IPv6 clients are counted per /64. In deploy/server/compose.yaml these variables are passed through only when set in .env (an empty value means the default).

Health checks (/healthz, /api/healthz), install.sh and /agent/compose.yaml are never limited. The existing per-IP limits on access requests and on OAuth registration and token calls stay in place on top of these (counted in Postgres as well).

The client IP comes from X-Forwarded-For only when the direct peer is in ORCH_TRUST_PROXY (default: private networks, i.e. the Caddy container, which sets the header itself; in Cloudflare Tunnel mode Caddy keeps the chain Cloudflare built). A client that reaches the server directly cannot choose its own IP by sending that header. If you publish the server port without Caddy, set ORCH_TRUST_PROXY=false.

These have defaults and are defined in apps/server/src/config/index.ts. To change one, add it to the server service environment in compose.yaml.

VariableDefaultMeaning
DISPATCHER_ENABLEDtrueRun the dispatcher
DISPATCHER_INTERVAL_MS5000Dispatcher tick
AUTO_OFFER_TTL_SEC60Pending auto offers expire after this
CONSENT_REQUEST_TTL_SEC1800Pending approval requests expire after this
USAGE_STALE_SEC900A usage report older than this makes a member ineligible for auto offers
BUDGET_SAFETY_RESERVE0Extra server-side reserve on top of the agent’s own 20 %
OPUS_FACTOR5Weight of Opus relative to Sonnet in estimates
PRO_WINDOW_WEIGHTED44000Weighted tokens of a Pro 5-hour window (base of S/M/L estimates)
EXEC_LEASE_GRACE_SEC900Grace added to task timeouts before an auto run is declared lost
AGENT_LEASE_SEC1800Running assignments of an agent unseen this long become lost
AGENT_HEARTBEAT_SEC30Agent heartbeat interval
AGENT_HEARTBEAT_TIMEOUT_MS90000Agent considered offline after this
AGENT_USAGE_SEC300Usage report interval
ORCH_AGENT_IMAGE_REPOghcr.io/theroxat/orch-agentImage repository offered for one-click agent updates
METRICS_ENABLEDfalseServe Prometheus metrics on /metrics (404 otherwise), see Observability
METRICS_TOKEN(empty)Bearer token required on /metrics when set (16 characters minimum)
OTEL_EXPORTER_OTLP_ENDPOINT(empty)OTLP/HTTP endpoint for OpenTelemetry traces; tracing is off while unset
NOTIFY_MAX_ATTEMPTS4Attempts per external notification delivery before giving up
NOTIFY_RETRY_BASE_MS2000First retry delay of a notification; each next one is 4x longer
NOTIFY_RETENTION_DAYS90In-app notifications older than this are deleted
NOTIFY_QUEUE_POLL_MS5000How often the delivery queue is polled for retries and leftovers of other instances
ORCH_INSTANCE_IDhostnameUnique name of this instance among those sharing the database (Running several server instances)
ORCH_CLUSTER_NAMEorchInstances with the same database and name cooperate; change it only to run separate deployments on one database
ORCH_INSTANCE_HEARTBEAT_MS10000Instance heartbeat; an instance silent for 3 heartbeats is considered gone
ORCH_LEADER_RETRY_MS5000How often a follower tries to take a free leadership (dispatcher, maintenance)

See Agent CLI reference for ~/orch/.env; the full list is in apps/agent/src/config.ts.

./dev copies .env.example to .env on first run. Besides the GITHUB_* and ORCH_ENCRYPTION_KEY values shared with production, it has ORCH_DEV_WEB_PORT (8470), ORCH_DEV_API_PORT (8471), POSTGRES_* placeholders, ORCH_DEV_ENROLL_TOKEN and ORCH_DEV_AGENT_CREDENTIAL for the dev agent. See Development and contributing.