4569657923
## What & why This is the system behind the Dashboard Agent — an assistant that answers questions about a project's runs, errors, queues, deploys and health, and can investigate failures end to end. The agent runs as a chat.agent task in its own Trigger project. It has no access to the main database or ClickHouse; all platform data is read through the public API using a delegated, read-only user token. Everything here is behind `canAccessDashboardAgent` and inert with the flag off. The UI that mounts the panel lands in #4529. ## Stack `#4418` (this, base) ← `#4529` UI ← `#4525` Watch ← `#4516` storybook gallery. The scenario/contract reference for the whole stack is `internal-packages/dashboard-agent/GUIDEBOOK.md` (it lands on the Watch branch): it states, per feature, what makes each thing happen and where that is decided. ## What's inside **Agent runtime and tools** — `internal-packages/dashboard-agent`: prompt, tool set (API reads, TRQL query, docs, navigation, evidence/investigations, repo source), conversation compaction, a prompt-prefix token budget pinned by snapshot test, and sampled LLM-judged turn evals. The package cannot import webapp server code, which is what makes the "no DB access" claim structural rather than a convention. **Contracts** — `internal-packages/dashboard-agent-contracts`: `trigger://` URIs, intents, and the block envelope every rendered card travels in. **Conversation store** — `internal-packages/dashboard-agent-db`: drizzle over postgres-js in its own `trigger_dashboard_agent` Postgres schema, plus one additive migration. **Auth boundary** — the user-actor token gains an optional environment claim; one guard (`userActorEnvironment.server.ts`) enforces it so routes don't each re-derive the rule. Token minting, cap ceiling, and the RBAC fallback path for self-hosted. **Transport** — webapp resource routes that mint the token and proxy each turn, and SDK-side mid-turn reconnect. **Public API the agent reads through** — orgs, projects, environments, runs, queue metrics, workers, a run's commit metadata, repo snapshot, reports, and `POST /api/v1/query`. **Reports** — the health report's layout is declared once and shared by the card, the markdown surface and the JSON/MCP surface, so the same report reads the same in the dashboard, the terminal and an editor. **Block renderers** — the report and investigation cards the flows above already emit (`app/components/dashboard-agent/`). The panel that hosts them, and the rest of the chat UI, is #4529. **Query safety and CSP** — see below. ## Key decisions - **The agent is a separate Trigger project, not webapp code.** It reads platform data over the public API with a delegated user-actor token whose `cap` ceilings it to read scopes. No Prisma, no ClickHouse, no webapp imports. - **The PAT-only auth helper now refuses user-actor tokens.** This is an intentional behavioral change: its callers consume only a bare userId and do not enforce delegated-token capabilities. Actor-aware routes continue through the scoped route builders instead. - **RBAC fallback builds a delegated token's ability from its own cap**, never the blanket ability a PAT gets (read-only when the token declares none). Without this, the agent's read-only cap would buy a write JWT on self-hosted. - **Org creation checks RBAC only for user-actor tokens, and only after the env gate**, so an install with `ORG_CREATION_API_ENABLED` off returns 404 rather than 403, and an ordinary PAT never consults an ability the route has no org to scope. Both orderings are pinned by test. - **The query path is read-only in depth.** TRQL rejects write statements at the grammar level (they don't parse, rather than being filtered), ClickHouse runs with `readonly=1`, and the org/project/env filters are injected server-side from the credential — the request body cannot widen scope. An unparseable query denies instead of falling through to the permissive resource. - **Document-wide img-src CSP.** Remote images are an outbound-request/exfiltration surface, so the policy permits only own-origin/data/blob, the required SSO avatar hosts, and the favicon endpoint. Operators can add exact origins through CSP_IMG_SRC_ALLOWLIST; wildcard hosts and bare schemes are intentionally not allowed. - **The chat transport reconnects on a mid-turn EOF** (`@trigger.dev/sdk`). A body that ends without a turn-complete is terminal only when the server says `X-Session-Settled: true`; otherwise the transport resubscribes from `lastEventId` with bounded backoff, and any record re-earns the budget. Previously a closed long-poll window or a proxy restart left the reply stuck as if still generating. - **Conversations live in their own datastore**, schema-scoped and foreign-key-free (it references `organizationId`/`userId` by id, because in cloud it is a different database). It is a display read-model for the History tab and transport resume; `chat.agent`'s object-store snapshot remains the model's source of truth. - **Deterministic first.** Reports and health checks contain no LLM — they are computed from the same data the dashboard shows, and the model only narrates and links them. That is what makes a number in an answer auditable. ## Testing - 63 new test files, run with `pnpm run test --filter webapp` and per-package vitest. Heaviest coverage on the auth boundary (`userActorPatOnlyBoundary`, `userActorTokenClaimsAndScopes`, `contextlessPatRoutes`, `rbacFallbackBranch`), TRQL read-only, the report layout, and the SDK reconnect. - The agent package has a separate eval lane (`pnpm run test:evals`, `vitest.eval.config.ts`) that hits the real model, so it never runs in `pnpm test`. - Live-tested against a local stack scenario by scenario; the GUIDEBOOK lists the condition each behaviour is expected under, which is what those runs were checked against. ## Changelog `.server-changes/dashboard-agent.md`, plus changesets for `@trigger.dev/core` (report schemas), `@trigger.dev/sdk` (chat reconnect) and the CLI's `mint-token` help text.
290 lines
40 KiB
Plaintext
290 lines
40 KiB
Plaintext
---
|
||
title: "Webapp"
|
||
description: "Environment variables for the webapp container."
|
||
sidebarTitle: "Webapp"
|
||
mode: "wide"
|
||
---
|
||
|
||
| Name | Required | Default | Description |
|
||
| :----------------------------------------------- | :------- | :-------------------- | :----------------------------------------------------------------------------------------------------------------- |
|
||
| **Secrets** | | | |
|
||
| `SESSION_SECRET` | Yes | — | Session encryption secret. Run: `openssl rand -hex 16` |
|
||
| `MAGIC_LINK_SECRET` | Yes | — | Magic link encryption secret. Run: `openssl rand -hex 16` |
|
||
| `ENCRYPTION_KEY` | Yes | — | Secret store encryption key. Run: `openssl rand -hex 16` |
|
||
| `MANAGED_WORKER_SECRET` | Yes | — | Managed worker secret. Must be set and match supervisor. Run: `openssl rand -hex 32` |
|
||
| `ALLOW_INSECURE_DEFAULT_SECRETS` | No | false | Boot even if a secret is still a known-insecure published default. Temporary escape hatch for values you can't safely rotate yet (see [Secrets](/self-hosting/kubernetes#secrets)). |
|
||
| **Domains & ports** | | | |
|
||
| `REMIX_APP_PORT` | No | 3030 | Remix app port. |
|
||
| `APP_ORIGIN` | Yes | http://localhost:3030 | App origin URL. |
|
||
| `LOGIN_ORIGIN` | Yes | http://localhost:3030 | Login origin URL. Most likely the same as `APP_ORIGIN`. |
|
||
| `API_ORIGIN` | No | `APP_ORIGIN` | API origin URL. |
|
||
| `STREAM_ORIGIN` | No | `APP_ORIGIN` | Realtime stream origin URL. |
|
||
| `ELECTRIC_ORIGIN` | No | http://localhost:3060 | Electric origin URL. |
|
||
| **Postgres** | | | |
|
||
| `DATABASE_URL` | Yes | — | PostgreSQL connection string. |
|
||
| `DIRECT_URL` | Yes | — | Direct DB connection string used for migrations etc. |
|
||
| `DATABASE_CONNECTION_LIMIT` | No | 10 | Max DB connections. |
|
||
| `DATABASE_POOL_TIMEOUT` | No | 60 | DB pool timeout (s). |
|
||
| `DATABASE_CONNECTION_TIMEOUT` | No | 20 | DB connect timeout (s). |
|
||
| `DATABASE_WRITER_POOL_TIMEOUT` | No | `DATABASE_POOL_TIMEOUT` | Writer pool timeout (s); overrides the shared default for the writer only. |
|
||
| `DATABASE_WRITER_CONNECTION_TIMEOUT` | No | `DATABASE_CONNECTION_TIMEOUT` | Writer connect timeout (s); overrides the shared default for the writer only. |
|
||
| `DATABASE_READ_REPLICA_URL` | No | `DATABASE_URL` | Read-replica DB string. |
|
||
| `DATABASE_READ_REPLICA_POOL_TIMEOUT` | No | `DATABASE_POOL_TIMEOUT` | Read-replica pool timeout (s); overrides the shared default for the replica only. |
|
||
| `DATABASE_READ_REPLICA_CONNECTION_TIMEOUT` | No | `DATABASE_CONNECTION_TIMEOUT` | Read-replica connect timeout (s); overrides the shared default for the replica only. |
|
||
| **Redis** | | | |
|
||
| `REDIS_HOST` | Yes | — | Redis host. |
|
||
| `REDIS_PORT` | Yes | — | Redis port. |
|
||
| `REDIS_READER_HOST` | No | `REDIS_HOST` | Redis reader host. |
|
||
| `REDIS_READER_PORT` | No | `REDIS_PORT` | Redis reader port. |
|
||
| `REDIS_USERNAME` | No | — | Redis username. |
|
||
| `REDIS_PASSWORD` | No | — | Redis password. |
|
||
| `REDIS_TLS_DISABLED` | No | — | Disable Redis TLS. |
|
||
| **Auth** | | | |
|
||
| `WHITELISTED_EMAILS` | No | — | Whitelisted emails regex. |
|
||
| `LOGIN_RATE_LIMITS_ENABLED` | No | true | Enable rate limiting on magic-link login. |
|
||
| `AUTH_GITHUB_CLIENT_ID` | No | — | GitHub client ID. |
|
||
| `AUTH_GITHUB_CLIENT_SECRET` | No | — | GitHub client secret. |
|
||
| `CSP_IMG_SRC_ALLOWLIST` | No | — | Extra hosts the dashboard may load images from, e.g. an SSO or avatar host serving profile images. A comma-separated list of exact origins (`https://sso.example.com`); a wildcard host such as `https://*.example.com` is refused. |
|
||
| **Email** | | | |
|
||
| `EMAIL_TRANSPORT` | No | — | Email transport type. One of `resend`, `smtp`, `aws-ses`. |
|
||
| `FROM_EMAIL` | No | — | From email address. |
|
||
| `REPLY_TO_EMAIL` | No | — | Reply-to email address. |
|
||
| `RESEND_API_KEY` | No | — | Resend API key. |
|
||
| `SMTP_HOST` | No | — | SMTP host. |
|
||
| `SMTP_PORT` | No | — | SMTP port. |
|
||
| `SMTP_SECURE` | No | — | SMTP secure flag. |
|
||
| `SMTP_USER` | No | — | SMTP user. |
|
||
| `SMTP_PASSWORD` | No | — | SMTP password. |
|
||
| `AWS_REGION` | No | — | AWS region for SES. |
|
||
| `AWS_ACCESS_KEY_ID` | No | — | AWS access key ID for SES. |
|
||
| `AWS_SECRET_ACCESS_KEY` | No | — | AWS secret access key for SES. |
|
||
| **Worker** | | | |
|
||
| `GRACEFUL_SHUTDOWN_TIMEOUT` | No | 60000 (1m) | Graceful shutdown timeout (ms). Affects shutdown time. |
|
||
| **Concurrency limits** | | | |
|
||
| `DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT` | No | 100 | Default env execution concurrency. |
|
||
| `DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT` | No | 300 | Default org execution concurrency, needs to be 3x env concurrency. |
|
||
| `DEFAULT_ENV_EXECUTION_CONCURRENCY_BURST_FACTOR` | No | 1.0 | Burst factor for env concurrency. |
|
||
| `DEFAULT_DEV_ENV_EXECUTION_ATTEMPTS` | No | 1 | Default max attempts for dev environment runs. |
|
||
| **Dev** | | | |
|
||
| `DEV_MAX_CONCURRENT_RUNS` | No | 25 | Sets the max concurrency for dev runs via the CLI. |
|
||
| `DEV_OTEL_EXPORTER_OTLP_ENDPOINT` | No | `APP_ORIGIN/otel` | OTel endpoint for dev runs. |
|
||
| **Rate limiting** | | | |
|
||
| `API_RATE_LIMIT_REFILL_INTERVAL` | No | 10s | API rate limit refill interval. |
|
||
| `API_RATE_LIMIT_MAX` | No | 750 | API rate limit max. |
|
||
| `API_RATE_LIMIT_REFILL_RATE` | No | 250 | API rate limit refill rate. |
|
||
| `API_RATE_LIMIT_REQUEST_LOGS_ENABLED` | No | 0 | API rate limit request logs. |
|
||
| `API_RATE_LIMIT_REJECTION_LOGS_ENABLED` | No | 1 | API rate limit rejection logs. |
|
||
| `API_RATE_LIMIT_LIMITER_LOGS_ENABLED` | No | 0 | API rate limit limiter logs. |
|
||
| `API_RATE_LIMIT_JWT_WINDOW` | No | 1m | API rate limit JWT window. |
|
||
| `API_RATE_LIMIT_JWT_TOKENS` | No | 60 | API rate limit JWT tokens. |
|
||
| `DEPLOYMENT_RATE_LIMIT_REFILL_INTERVAL` | No | 10s | Deployment endpoints rate limit refill interval. |
|
||
| `DEPLOYMENT_RATE_LIMIT_MAX` | No | 1500 | Deployment endpoints rate limit max. |
|
||
| `DEPLOYMENT_RATE_LIMIT_REFILL_RATE` | No | 500 | Deployment endpoints rate limit refill rate. |
|
||
| `DEPLOYMENT_RATE_LIMIT_REQUEST_LOGS_ENABLED` | No | 0 | Deployment endpoints rate limit request logs. |
|
||
| `DEPLOYMENT_RATE_LIMIT_REJECTION_LOGS_ENABLED` | No | 1 | Deployment endpoints rate limit rejection logs. |
|
||
| `DEPLOYMENT_RATE_LIMIT_LIMITER_LOGS_ENABLED` | No | 0 | Deployment endpoints rate limit limiter logs. |
|
||
| **Deploy & Registry** | | | |
|
||
| `DEPLOY_REGISTRY_HOST` | Yes | — | Deploy registry host. |
|
||
| `DEPLOY_REGISTRY_USERNAME` | No | — | Deploy registry username. |
|
||
| `DEPLOY_REGISTRY_PASSWORD` | No | — | Deploy registry password. |
|
||
| `DEPLOY_REGISTRY_NAMESPACE` | No | trigger | Deploy registry namespace. |
|
||
| `DEPLOY_REGISTRY_ECR_DEFAULT_REPOSITORY_POLICY` | No | — | Raw IAM policy JSON applied via SetRepositoryPolicy to every ECR repo created by the webapp. Use to grant cross-account pull access to EKS workers when the ECR account is separate from the cluster account. |
|
||
| `DEPLOY_IMAGE_PLATFORM` | No | linux/amd64 | Deploy image platform, same values as docker `--platform` flag. |
|
||
| `DEPLOY_TIMEOUT_MS` | No | 480000 (8m) | Deploy timeout (ms). |
|
||
| `DEPLOY_QUEUE_TIMEOUT_MS` | No | 900000 (15m) | Deploy queue timeout (ms). |
|
||
| **Object store (S3)** | | | |
|
||
| `OBJECT_STORE_BASE_URL` | No | — | Object store base URL (default provider). |
|
||
| `OBJECT_STORE_BUCKET` | No | — | Object store bucket name (default provider). |
|
||
| `OBJECT_STORE_ACCESS_KEY_ID` | No | — | Object store access key (default provider). |
|
||
| `OBJECT_STORE_SECRET_ACCESS_KEY` | No | — | Object store secret key (default provider). |
|
||
| `OBJECT_STORE_REGION` | No | — | Object store region (default provider). |
|
||
| `OBJECT_STORE_SERVICE` | No | s3 | Object store service (default provider). |
|
||
| `OBJECT_STORE_DEFAULT_PROTOCOL` | No | — | Protocol for new uploads (e.g. `s3`, `r2`). Enables protocol-prefixed storage. See migration guide below. |
|
||
| `OBJECT_STORE_{PROTOCOL}_BASE_URL` | No | — | Named provider base URL (replace `{PROTOCOL}`, e.g. `OBJECT_STORE_S3_BASE_URL`). |
|
||
| `OBJECT_STORE_{PROTOCOL}_ACCESS_KEY_ID` | No | — | Named provider access key. |
|
||
| `OBJECT_STORE_{PROTOCOL}_SECRET_ACCESS_KEY` | No | — | Named provider secret key. |
|
||
| `OBJECT_STORE_{PROTOCOL}_REGION` | No | — | Named provider region. |
|
||
| `OBJECT_STORE_{PROTOCOL}_SERVICE` | No | — | Named provider service. |
|
||
| `ARTIFACTS_OBJECT_STORE_BUCKET` | No | — | Optional separate bucket for artifacts. If not set, uses main object store. |
|
||
| `ARTIFACTS_OBJECT_STORE_BASE_URL` | No | — | Optional artifacts store base URL. |
|
||
| `ARTIFACTS_OBJECT_STORE_ACCESS_KEY_ID` | No | — | Optional artifacts store access key. |
|
||
| `ARTIFACTS_OBJECT_STORE_SECRET_ACCESS_KEY` | No | — | Optional artifacts store secret key. |
|
||
| `ARTIFACTS_OBJECT_STORE_REGION` | No | — | Optional artifacts store region. |
|
||
| **Alerts** | | | |
|
||
| `ORG_SLACK_INTEGRATION_CLIENT_ID` | No | — | Slack client ID. Required for Slack alerts. |
|
||
| `ORG_SLACK_INTEGRATION_CLIENT_SECRET` | No | — | Slack client secret. Required for Slack alerts. |
|
||
| `ALERT_EMAIL_TRANSPORT` | No | — | Alert email transport. |
|
||
| `ALERT_FROM_EMAIL` | No | — | Alert from email. |
|
||
| `ALERT_REPLY_TO_EMAIL` | No | — | Alert reply-to email. |
|
||
| `ALERT_RESEND_API_KEY` | No | — | Alert Resend API key. |
|
||
| `ALERT_SMTP_HOST` | No | — | Alert SMTP host. |
|
||
| `ALERT_SMTP_PORT` | No | — | Alert SMTP port. |
|
||
| `ALERT_SMTP_SECURE` | No | — | Alert SMTP secure. |
|
||
| `ALERT_SMTP_USER` | No | — | Alert SMTP user. |
|
||
| `ALERT_SMTP_PASSWORD` | No | — | Alert SMTP password. |
|
||
| **Limits** | | | |
|
||
| `TASK_PAYLOAD_OFFLOAD_THRESHOLD` | No | 524288 (512KB) | Max task payload size before offloading to S3. |
|
||
| `TASK_PAYLOAD_MAXIMUM_SIZE` | No | 3145728 (3MB) | Max task payload size. |
|
||
| `BATCH_TASK_PAYLOAD_MAXIMUM_SIZE` | No | 1000000 (1MB) | Max batch payload size. |
|
||
| `BATCH_CONCURRENCY_LIMIT_DEFAULT` | No | 5 | Default concurrency for batch processing. |
|
||
| `BATCH_RATE_LIMIT_REFILL_RATE` | No | 100 | Batch rate limit refill rate. |
|
||
| `BATCH_RATE_LIMIT_MAX` | No | 1200 | Batch rate limit max. |
|
||
| `BATCH_RATE_LIMIT_REFILL_INTERVAL` | No | 10s | Batch rate limit refill interval. |
|
||
| `TASK_RUN_METADATA_MAXIMUM_SIZE` | No | 262144 (256KB) | Max metadata size. |
|
||
| `MAX_BATCH_V2_TRIGGER_ITEMS` | No | 500 | Max batch size (legacy v2 API). |
|
||
| `STREAMING_BATCH_MAX_ITEMS` | No | 1000 | Max items in streaming batch (v3 API, requires SDK 4.3.1+). |
|
||
| `STREAMING_BATCH_ITEM_MAXIMUM_SIZE` | No | 3145728 (3MB) | Max size per item in streaming batch. |
|
||
| `STREAMING_BATCH_INGEST_CONCURRENCY` | No | 10 | Items ingested concurrently per streaming batch request. Peak memory ≈ this × item size. Set to 1 for sequential. |
|
||
| **OTel limits** | | | |
|
||
| `TRIGGER_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT` | No | 1024 | OTel span attribute count limit. |
|
||
| `TRIGGER_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT` | No | 1024 | OTel log attribute count limit. |
|
||
| `TRIGGER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` | No | 131072 | OTel span attribute value length limit. |
|
||
| `TRIGGER_OTEL_LOG_ATTRIBUTE_VALUE_LENGTH_LIMIT` | No | 131072 | OTel log attribute value length limit. |
|
||
| `TRIGGER_OTEL_SPAN_EVENT_COUNT_LIMIT` | No | 10 | OTel span event count limit. |
|
||
| `TRIGGER_OTEL_LINK_COUNT_LIMIT` | No | 2 | OTel link count limit. |
|
||
| `TRIGGER_OTEL_ATTRIBUTE_PER_LINK_COUNT_LIMIT` | No | 10 | OTel attribute per link count limit. |
|
||
| `TRIGGER_OTEL_ATTRIBUTE_PER_EVENT_COUNT_LIMIT` | No | 10 | OTel attribute per event count limit. |
|
||
| `SERVER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` | No | 8192 | OTel span attribute value length limit. |
|
||
| **Task events** | | | |
|
||
| `EVENT_REPOSITORY_DEFAULT_STORE` | No | postgres | Where to store task events. Set to `clickhouse_v2` to store in ClickHouse (recommended for production). |
|
||
| `EVENT_REPOSITORY_POSTGRES_WRITES_DISABLED` | No | 0 | Skip all PostgreSQL task-event writes (set to `1`). Only enable when `EVENT_REPOSITORY_DEFAULT_STORE` is `clickhouse_v2`, otherwise task events are lost. |
|
||
| **Realtime** | | | |
|
||
| `REALTIME_STREAM_VERSION` | No | v1 | Stream version exposed to tasks via the `TRIGGER_REALTIME_STREAM_VERSION` variable. Distinct from `REALTIME_STREAMS_DEFAULT_VERSION`. One of `v1`, `v2`. |
|
||
| `REALTIME_STREAM_MAX_LENGTH` | No | 1000 | Realtime stream max length. |
|
||
| `REALTIME_STREAM_TTL` | No | 86400 (1d) | Realtime stream TTL (s). |
|
||
| `REALTIME_STREAMS_DEFAULT_VERSION` | No | v1 | Server-side default the webapp uses when a stream request doesn't pin a version (modern SDKs request `v2`). The Docker and Helm self-hosting defaults set this to `v2`. One of `v1`, `v2`. |
|
||
| `REALTIME_STREAMS_S2_BASIN` | No | — | S2 basin that holds v2 realtime streams. Required for `v2`. Must be at least 8 characters. |
|
||
| `REALTIME_STREAMS_S2_ENDPOINT` | No | — | Custom S2 API endpoint, including the `/v1` suffix (e.g. `http://s2/v1` for the bundled s2-lite). Omit to use hosted S2 at s2.dev. |
|
||
| `REALTIME_STREAMS_S2_SKIP_ACCESS_TOKENS` | No | false | Skip minting per-stream access tokens. Set to `true` for s2-lite, which needs no authentication. |
|
||
| `REALTIME_STREAMS_S2_ACCESS_TOKEN` | No | — | S2 access token. Required for hosted S2 unless `REALTIME_STREAMS_S2_SKIP_ACCESS_TOKENS` is `true`. |
|
||
| **Bootstrap** | | | |
|
||
| `TRIGGER_BOOTSTRAP_ENABLED` | No | 0 | Trigger bootstrap enabled. |
|
||
| `TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME` | No | — | Trigger bootstrap worker group name. |
|
||
| `TRIGGER_BOOTSTRAP_WORKER_TOKEN_PATH` | No | — | Trigger bootstrap worker token path. |
|
||
| **Run engine** | | | |
|
||
| `RUN_ENGINE_WORKER_COUNT` | No | 4 | Run engine worker count. |
|
||
| `RUN_ENGINE_TASKS_PER_WORKER` | No | 10 | Run engine tasks per worker. |
|
||
| `RUN_ENGINE_WORKER_CONCURRENCY_LIMIT` | No | 10 | Run engine worker concurrency limit. |
|
||
| `RUN_ENGINE_WORKER_POLL_INTERVAL` | No | 100 | Run engine worker poll interval (ms). |
|
||
| `RUN_ENGINE_WORKER_IMMEDIATE_POLL_INTERVAL` | No | 100 | Run engine worker immediate poll interval (ms). |
|
||
| `RUN_ENGINE_WORKER_SHUTDOWN_TIMEOUT_MS` | No | 60000 (1m) | Run engine worker shutdown timeout (ms). |
|
||
| `RUN_ENGINE_RATE_LIMIT_REFILL_INTERVAL` | No | 10s | Run engine rate limit refill interval. |
|
||
| `RUN_ENGINE_RATE_LIMIT_MAX` | No | 1200 | Run engine rate limit max. |
|
||
| `RUN_ENGINE_RATE_LIMIT_REFILL_RATE` | No | 400 | Run engine rate limit refill rate. |
|
||
| `RUN_ENGINE_RATE_LIMIT_REQUEST_LOGS_ENABLED` | No | 0 | Run engine rate limit request logs. |
|
||
| `RUN_ENGINE_RATE_LIMIT_REJECTION_LOGS_ENABLED` | No | 1 | Run engine rate limit rejection logs. |
|
||
| `RUN_ENGINE_RATE_LIMIT_LIMITER_LOGS_ENABLED` | No | 0 | Run engine rate limit limiter logs. |
|
||
| `RUN_ENGINE_DEFAULT_MAX_TTL` | No | — | Maximum TTL for all runs (e.g. "14d"). Runs without a TTL use this as default; runs with a larger TTL are clamped. |
|
||
| `MAXIMUM_DEV_QUEUE_SIZE` | No | — | Maximum queued runs per queue in development environments. |
|
||
| `MAXIMUM_DEPLOYED_QUEUE_SIZE` | No | — | Maximum queued runs per queue in deployed (staging/prod) environments. |
|
||
| **Misc** | | | |
|
||
| `TRIGGER_TELEMETRY_DISABLED` | No | — | Disable telemetry. |
|
||
| `NODE_MAX_OLD_SPACE_SIZE` | No | 8192 | Maximum memory allocation for Node.js heap in MiB (e.g. "4096" for 4GB). |
|
||
| `OPENAI_API_KEY` | No | — | OpenAI API key. |
|
||
| `MACHINE_PRESETS_OVERRIDE_PATH` | No | — | Path to machine presets override file. See [machine overrides](/self-hosting/overview#machine-overrides). |
|
||
| `APP_ENV` | No | `NODE_ENV` | App environment. Used for things like the title tag. |
|
||
| `ADMIN_EMAILS` | No | — | Regex of user emails to automatically promote to admin on signup. Does not apply to existing users. |
|
||
| `EVENT_LOOP_MONITOR_ENABLED` | No | 1 | Node.js event loop lag monitor. |
|
||
|
||
## Multi-Provider Object Storage
|
||
|
||
The object storage system supports multiple S3-compatible providers (R2, S3, GCS, MinIO, etc.) using protocol prefixes. This enables migrating between providers without breaking existing runs.
|
||
|
||
### How It Works
|
||
|
||
When data exceeds the configured threshold (`TASK_PAYLOAD_OFFLOAD_THRESHOLD`), it's uploaded to object storage. The storage location is saved in the database with an optional protocol prefix:
|
||
|
||
- **With protocol**: `s3://run_abc/payload.json` or `r2://batch_123/item_0/payload.json`
|
||
- **Without protocol** (legacy): `batch_123/item_0/payload.json` (uses default provider)
|
||
|
||
### Configuration
|
||
|
||
#### Default Provider (Backward Compatible)
|
||
|
||
The default provider is used for data without a protocol prefix:
|
||
|
||
```bash
|
||
# Default provider (backward compatible - no protocol prefix)
|
||
OBJECT_STORE_BASE_URL=https://r2.example.com
|
||
OBJECT_STORE_ACCESS_KEY_ID=...
|
||
OBJECT_STORE_SECRET_ACCESS_KEY=...
|
||
OBJECT_STORE_REGION=auto
|
||
OBJECT_STORE_SERVICE=s3
|
||
```
|
||
|
||
#### Named Providers
|
||
|
||
Named providers are accessed via protocol-prefixed URIs. Configure them using `OBJECT_STORE_{PROTOCOL}_*` variables:
|
||
|
||
```bash
|
||
# S3 provider (accessed via s3:// prefix)
|
||
OBJECT_STORE_S3_BASE_URL=https://s3.amazonaws.com
|
||
OBJECT_STORE_S3_ACCESS_KEY_ID=...
|
||
OBJECT_STORE_S3_SECRET_ACCESS_KEY=...
|
||
OBJECT_STORE_S3_REGION=us-east-1
|
||
OBJECT_STORE_S3_SERVICE=s3
|
||
|
||
# R2 provider (accessed via r2:// prefix)
|
||
OBJECT_STORE_R2_BASE_URL=https://...r2.cloudflarestorage.com
|
||
OBJECT_STORE_R2_ACCESS_KEY_ID=...
|
||
OBJECT_STORE_R2_SECRET_ACCESS_KEY=...
|
||
OBJECT_STORE_R2_REGION=auto
|
||
OBJECT_STORE_R2_SERVICE=s3
|
||
```
|
||
|
||
#### Default Protocol for New Uploads
|
||
|
||
Set `OBJECT_STORE_DEFAULT_PROTOCOL` to specify which provider to use for new uploads:
|
||
|
||
```bash
|
||
# Use S3 for new uploads (old data without prefix still uses default provider)
|
||
OBJECT_STORE_DEFAULT_PROTOCOL=s3
|
||
```
|
||
|
||
### Migration Guide
|
||
|
||
To migrate from R2 to S3 without breaking existing runs:
|
||
|
||
<Steps>
|
||
<Step title="Configure S3 provider">
|
||
Add S3 credentials as a named provider:
|
||
|
||
```bash
|
||
OBJECT_STORE_S3_BASE_URL=https://s3.amazonaws.com
|
||
OBJECT_STORE_S3_ACCESS_KEY_ID=...
|
||
OBJECT_STORE_S3_SECRET_ACCESS_KEY=...
|
||
OBJECT_STORE_S3_REGION=us-east-1
|
||
```
|
||
|
||
Keep your existing `OBJECT_STORE_*` variables (R2) as the default provider.
|
||
|
||
</Step>
|
||
|
||
<Step title="Test the configuration">
|
||
Restart the webapp and verify both providers work:
|
||
|
||
- Old runs (no prefix) should still access R2
|
||
- New runs with `s3://` prefix should use S3
|
||
|
||
</Step>
|
||
|
||
<Step title="Switch to S3 for new uploads">
|
||
Set the default protocol to use S3 for new uploads:
|
||
|
||
```bash
|
||
OBJECT_STORE_DEFAULT_PROTOCOL=s3
|
||
```
|
||
|
||
After this change:
|
||
|
||
- New data uses `s3://` prefix and goes to S3
|
||
- Old data (no prefix) still uses R2
|
||
- Data with explicit protocol uses the corresponding provider
|
||
|
||
</Step>
|
||
|
||
<Step title="Optionally decommission R2">
|
||
Once all active runs using R2 data have completed (check your data retention policies), you can remove the R2 credentials. Keep `OBJECT_STORE_DEFAULT_PROTOCOL=s3` to ensure new data continues using S3.
|
||
</Step>
|
||
</Steps>
|