Skip to content

Commit eb32a7f

Browse files
committed
feat(webapp): add RUNTIME_API_ORIGIN to decouple runner traffic from external origin
The webapp publishes `API_ORIGIN` to runner pods as `TRIGGER_API_URL`, so runner-to-webapp traffic flows back through whatever URL is configured for external clients. Self-hosting behind a tracing-enabled gateway (Envoy, Istio, kgateway, ...) breaks the parent->child run link in trigger.dev's run-detail tree because the gateway's W3C `traceparent` rewrite on egress overwrites the SDK's `triggerAndWait()` span id. The webapp then writes that gateway-generated span id as the child run's `parentSpanId`, which never reaches the trigger event store, so the child renders as an orphan in the UI. Operators can split the two concerns without sacrificing external auth/ callbacks/UI flows that rely on the public `API_ORIGIN`: - Set `RUNTIME_API_ORIGIN=http://<service>.<namespace>:<port>` (k8s) or `http://webapp:3000` (docker) to keep runner->webapp traffic on a cluster-internal hop that bypasses the gateway. - Leave `API_ORIGIN` on the public URL so the dashboard, magic-link emails, waitpoint callbacks, and API `apiUrl` responses keep working for external clients. Scope is intentionally limited to MANAGED (deployed) runs. Dev CLI runs keep the original `API_ORIGIN`/`APP_ORIGIN` chain so a developer running `trigger.dev dev` from outside the cluster does not lose connectivity. `STREAM_ORIGIN` is still honored as a dedicated stream endpoint when set; `RUNTIME_API_ORIGIN` takes precedence over it for `TRIGGER_STREAM_URL` so the bypass keeps streams on the same internal hop by default. The new env is optional and falls back to `API_ORIGIN`/`APP_ORIGIN`, so existing deployments are unaffected. An empty string is normalized to `undefined` in the zod schema so blank `${RUNTIME_API_ORIGIN:-}` passthroughs from caller environments do not short-circuit the fallback chain. Helm chart and Docker Compose are wired to forward the value to the webapp container. Refs: #2821
1 parent 12d2125 commit eb32a7f

6 files changed

Lines changed: 68 additions & 2 deletions

File tree

apps/webapp/app/env.server.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,21 @@ const EnvironmentSchema = z
127127
LOGIN_RATE_LIMITS_ENABLED: BoolEnv.default(true),
128128
APP_ORIGIN: z.string().default("http://localhost:3030"),
129129
API_ORIGIN: z.string().optional(),
130+
// Origin that the webapp publishes to MANAGED (deployed) runner pods as
131+
// both `TRIGGER_API_URL` and (as the first fallback) `TRIGGER_STREAM_URL`.
132+
// When self-hosting behind a tracing-enabled gateway (Envoy/Istio/etc.)
133+
// that rewrites the W3C `traceparent` on egress, point this at an
134+
// in-cluster service URL so runner-to-webapp traffic stays inside the
135+
// cluster and the parent->child run link in the trace tree is preserved.
136+
// Intentionally NOT used for dev (CLI) task runs, which usually run on a
137+
// developer's machine outside the cluster and would lose connectivity if
138+
// forced onto an in-cluster URL. Empty string is normalized to unset so
139+
// blank `${RUNTIME_API_ORIGIN:-}` passthroughs from caller environments
140+
// don't short-circuit the `??` fallback chain.
141+
RUNTIME_API_ORIGIN: z
142+
.string()
143+
.optional()
144+
.transform((v) => v || undefined),
130145
STREAM_ORIGIN: z.string().optional(),
131146
ELECTRIC_ORIGIN: z.string().default("http://localhost:3060"),
132147
// A comma separated list of electric origins to shard into different electric instances by environmentId

apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,13 @@ function renameVariables(variables: EnvironmentVariable[], renameMap: Record<str
941941
});
942942
}
943943

944+
/**
945+
* Resolves trigger-side built-in environment variables that are merged into a
946+
* task run's env but can be overridden by user-defined variables on the
947+
* environment. Values come from server-level `env` and are surfaced to runners
948+
* so they can pick up rollouts (e.g. the realtime stream version) without a
949+
* redeploy.
950+
*/
944951
async function resolveOverridableTriggerVariables(
945952
runtimeEnvironment: RuntimeEnvironmentForEnvRepo
946953
) {
@@ -954,6 +961,14 @@ async function resolveOverridableTriggerVariables(
954961
return result;
955962
}
956963

964+
/**
965+
* Resolves built-in environment variables that are injected into dev (CLI) task
966+
* runs. Dev CLI typically runs on a developer's machine outside any cluster,
967+
* so the runner-bypass `RUNTIME_API_ORIGIN` (which usually points at an
968+
* in-cluster service URL) is intentionally NOT applied here -- using it would
969+
* make the URL unreachable for the dev CLI. Dev keeps the original
970+
* `API_ORIGIN`/`STREAM_ORIGIN`/`APP_ORIGIN` chain.
971+
*/
957972
async function resolveBuiltInDevVariables(runtimeEnvironment: RuntimeEnvironmentForEnvRepo) {
958973
let result: Array<EnvironmentVariable> = [
959974
{
@@ -1080,6 +1095,12 @@ async function resolveBuiltInDevVariables(runtimeEnvironment: RuntimeEnvironment
10801095
return [...result, ...commonVariables];
10811096
}
10821097

1098+
/**
1099+
* Resolves the OpenTelemetry collector endpoint advertised to dev (CLI) task
1100+
* runs. Defaults to the webapp's own `/otel` route under `APP_ORIGIN` so a
1101+
* vanilla self-host works without extra wiring; `DEV_OTEL_EXPORTER_OTLP_ENDPOINT`
1102+
* can override it to point spans/logs at an external collector.
1103+
*/
10831104
async function resolveOverridableOtelDevVariables(
10841105
runtimeEnvironment: RuntimeEnvironmentForEnvRepo
10851106
) {
@@ -1093,6 +1114,15 @@ async function resolveOverridableOtelDevVariables(
10931114
return result;
10941115
}
10951116

1117+
/**
1118+
* Resolves built-in environment variables that are injected into managed
1119+
* (deployed) task runs. `TRIGGER_API_URL` and `TRIGGER_STREAM_URL` prefer
1120+
* `RUNTIME_API_ORIGIN` over `API_ORIGIN`/`STREAM_ORIGIN` so self-hosted
1121+
* deployments can keep runner-to-webapp traffic on a cluster-internal hop
1122+
* (bypassing tracing-enabled gateways that rewrite the W3C `traceparent`
1123+
* header on egress) without affecting the public origins exposed to external
1124+
* clients.
1125+
*/
10961126
async function resolveBuiltInProdVariables(
10971127
runtimeEnvironment: RuntimeEnvironmentForEnvRepo,
10981128
parentEnvironment?: RuntimeEnvironmentForEnvRepo
@@ -1104,11 +1134,11 @@ async function resolveBuiltInProdVariables(
11041134
},
11051135
{
11061136
key: "TRIGGER_API_URL",
1107-
value: env.API_ORIGIN ?? env.APP_ORIGIN,
1137+
value: env.RUNTIME_API_ORIGIN ?? env.API_ORIGIN ?? env.APP_ORIGIN,
11081138
},
11091139
{
11101140
key: "TRIGGER_STREAM_URL",
1111-
value: env.STREAM_ORIGIN ?? env.API_ORIGIN ?? env.APP_ORIGIN,
1141+
value: env.RUNTIME_API_ORIGIN ?? env.STREAM_ORIGIN ?? env.API_ORIGIN ?? env.APP_ORIGIN,
11121142
},
11131143
{
11141144
key: "TRIGGER_RUNTIME_WAIT_THRESHOLD_IN_MS",

hosting/docker/.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ API_ORIGIN=http://localhost:8030
4646
DEV_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8030/otel
4747
# You may need to set this when testing locally or when using the combined setup
4848
# API_ORIGIN=http://webapp:3000
49+
# Optional: origin advertised to MANAGED (deployed) runner pods as both
50+
# TRIGGER_API_URL and TRIGGER_STREAM_URL (intentional: keeps all managed
51+
# runner traffic on the same bypass hop). Dev (CLI) task runs are NOT
52+
# affected -- they keep using API_ORIGIN/APP_ORIGIN so a developer running
53+
# `trigger.dev dev` from outside the cluster doesn't lose connectivity.
54+
# Set this to an in-cluster service URL when running behind a tracing-enabled
55+
# gateway that rewrites the W3C `traceparent` header on egress (e.g. Envoy/
56+
# Istio with tracing on). If you need streams on a dedicated endpoint (CDN,
57+
# etc.), keep RUNTIME_API_ORIGIN unset and use STREAM_ORIGIN instead.
58+
# RUNTIME_API_ORIGIN=http://webapp:3000
4959

5060
# Webapp - memory management
5161
# - This sets the maximum memory allocation for Node.js heap in MiB (e.g. "4096" for 4GB)

hosting/docker/webapp/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ services:
4343
APP_ORIGIN: ${APP_ORIGIN:-http://localhost:8030}
4444
LOGIN_ORIGIN: ${LOGIN_ORIGIN:-http://localhost:8030}
4545
API_ORIGIN: ${API_ORIGIN:-http://localhost:8030}
46+
RUNTIME_API_ORIGIN: ${RUNTIME_API_ORIGIN:-}
4647
ELECTRIC_ORIGIN: http://electric:3000
4748
DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/main?schema=public&sslmode=disable}
4849
DIRECT_URL: ${DIRECT_URL:-postgresql://postgres:postgres@postgres:5432/main?schema=public&sslmode=disable}

hosting/k8s/helm/templates/webapp.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ spec:
186186
value: {{ .Values.webapp.loginOrigin | quote }}
187187
- name: API_ORIGIN
188188
value: {{ .Values.webapp.apiOrigin | quote }}
189+
{{- with .Values.webapp.runtimeApiOrigin }}
190+
- name: RUNTIME_API_ORIGIN
191+
value: {{ . | quote }}
192+
{{- end }}
189193
- name: ELECTRIC_ORIGIN
190194
value: {{ include "trigger-v4.electric.url" . | quote }}
191195
{{- if include "trigger-v4.postgres.useSecretUrl" . }}

hosting/k8s/helm/values.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ webapp:
6868
appOrigin: "http://localhost:3040"
6969
loginOrigin: "http://localhost:3040"
7070
apiOrigin: "http://localhost:3040"
71+
# Origin advertised to runner pods as TRIGGER_API_URL.
72+
# When unset (default), runners use apiOrigin/appOrigin. Set this to an
73+
# in-cluster service URL to keep runner->webapp traffic inside the cluster,
74+
# bypassing gateways/proxies (e.g. Envoy with tracing enabled) that rewrite
75+
# the W3C `traceparent` header on egress and break the parent->child run link.
76+
runtimeApiOrigin: ""
7177

7278
replicaCount: 1
7379

0 commit comments

Comments
 (0)