Skip to content

Commit a8a8785

Browse files
ericallamTrigger.dev RepoOps
authored andcommitted
feat(core,cli,supervisor): bun-1.4 runtime (node-less, Bun 1.4.4) plus a bun-legacy alias
Adds the `bun-1.4` runtime. `runtime: "bun-1.4"` deploys on Bun 1.4.4 in a Bun-only image with no Node.js binary, and the run controller itself runs under Bun. `runtime: "bun"` keeps today's behavior (Bun 1.3.3 with Node.js 20 alongside it); `bun-legacy` is accepted as an explicit spelling of the same runtime. Bun 1.4 rejects the `--no-warnings=ExperimentalWarning` flag the worker was started with, so bun workers now get the bare `--no-warnings` form. The supervisor, build plugins, and rsc extension recognise every bun runtime instead of only the plain `bun` value. Docs cover the runtimes, that the `bun-1.4` image contains no Node.js binary, and the open Bun 1.4 `node:http2` client regression. Mono-RevId: 29aae0f8cb56dfb912865e00b72169d26b12aaf7
1 parent bff268d commit a8a8785

16 files changed

Lines changed: 223 additions & 31 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"trigger.dev": patch
3+
"@trigger.dev/core": patch
4+
"@trigger.dev/rsc": patch
5+
---
6+
7+
Adds the `bun-1.4` runtime. `runtime: "bun-1.4"` deploys on Bun 1.4.4 in a Bun-only image with no Node.js binary, and the run controller itself runs under Bun. `runtime: "bun"` is unchanged (Bun 1.3 with Node.js alongside it); `bun-legacy` is accepted as an explicit spelling of the same runtime. On Bun 1.4 the worker no longer crashes at startup on the `--no-warnings=ExperimentalWarning` flag.

‎apps/supervisor/src/workloadManager/kubernetes.test.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,10 @@ describe("runnerSecurityContext", () => {
222222
});
223223
});
224224

225-
it("pins bun's own uid, which differs from node's", () => {
226-
expect(runnerSecurityContext("restricted", 1000, "bun")?.runAsUser).toBe(1001);
225+
it("pins bun's own uid, which differs from node's, for every bun runtime", () => {
226+
for (const runtime of ["bun", "bun-legacy", "bun-1.4"]) {
227+
expect(runnerSecurityContext("restricted", 1000, runtime)?.runAsUser).toBe(1001);
228+
}
227229
});
228230

229231
it("falls back to the configured uid when the runtime is unknown", () => {

‎apps/supervisor/src/workloadManager/kubernetesPodSpec.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { k8s } from "../clients/kubernetes.js";
2+
import { isBunRuntime } from "@trigger.dev/core/v3/build";
23

34
/**
45
* An empty label is the documented off-switch, leaving the pod unpinned. The Helm
@@ -146,7 +147,7 @@ export function runnerSecurityContext(
146147
allowPrivilegeEscalation: false,
147148
capabilities: { drop: ["ALL"] },
148149
...(level === "restricted"
149-
? { runAsNonRoot: true, runAsUser: runtime === "bun" ? BUN_RUN_AS_USER : runAsUser }
150+
? { runAsNonRoot: true, runAsUser: isBunRuntime(runtime) ? BUN_RUN_AS_USER : runAsUser }
150151
: {}),
151152
};
152153
}

‎docs/config/config-file.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ After each call we restore the few attributes we use to link a span to its run,
329329

330330
If you don't set a `runtime`, your tasks run on the current Node.js LTS. Setting `runtime: "node"` does the same thing — see the Node.js versions section below to pin a specific version instead.
331331

332-
We currently only officially support the `node` runtime, but you can try our experimental `bun` runtime by setting the `runtime` option in your config file:
332+
We currently only officially support the `node` runtime, but you can try our experimental Bun runtimes by setting the `runtime` option in your config file:
333333

334334
```ts trigger.config.ts
335335
import { defineConfig } from "@trigger.dev/sdk";
@@ -341,7 +341,7 @@ export default defineConfig({
341341
});
342342
```
343343

344-
See our [Bun guide](/guides/frameworks/bun) for more information.
344+
`bun` runs on Bun 1.3 with a Node.js binary alongside it. `bun-1.4` runs on a Bun-only image with no Node.js binary. See our [Bun guide](/guides/frameworks/bun) for the differences.
345345

346346
### Node.js versions
347347

‎docs/guides/frameworks/bun.mdx‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,26 @@ import CliViewRunStep from "/snippets/step-view-run.mdx";
1515
</Warning>
1616

1717
<Note>
18-
**Supported Bun version:** Deployed tasks run on Bun 1.3.3. For local development, use Bun 1.3.x
19-
for compatibility.
18+
**Supported Bun versions:** `runtime: "bun"` deploys on Bun 1.3.3 with a Node.js 20 binary
19+
alongside it. Opt in to `runtime: "bun-1.4"` (Bun 1.4.4) for a
20+
Bun-only image. For local development, match the Bun minor you deploy on.
21+
</Note>
22+
23+
## Choosing a Bun runtime
24+
25+
| `runtime` | Bun | Node.js binary in the image | Notes |
26+
| ------------ | ------ | --------------------------- | ------------------------------------------------------------------------ |
27+
| `bun` | 1.3.3 | Yes (Node.js 20) | The default, also spelled `bun-legacy`. Use this if a task shells out to `node`. |
28+
| `bun-1.4` | 1.4.4 | No | Bun-only image. See the Bun 1.4 note below. |
29+
30+
The `bun-1.4` image contains **no Node.js binary**. Anything in your task that spawns
31+
`node` (a CLI tool, a `postinstall` script, a library that shells out) will fail on it. Keep those
32+
projects on `bun`, or install Node.js yourself with a build extension.
33+
34+
<Note>
35+
**Bun 1.4:** Bun 1.4.x has an open regression in its `node:http2` client
36+
([oven-sh/bun#39796](https://github.com/oven-sh/bun/issues/39796)). If your tasks use HTTP/2
37+
clients, for example some gRPC or Google Cloud SDKs, use `bun` until it is fixed.
2038
</Note>
2139

2240
<Prerequisites framework="Bun" />

‎docs/snippets/node-versions.mdx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ Trigger.dev runs your deployed tasks on one of these runtimes:
44
- Node.js `node-22` — `22.23.4`
55
- Node.js `node-24` — `24.22.1`
66
- Node.js `node-26` — `26.12.1`
7-
- Bun `bun` — `1.3.3`
7+
- Bun `bun` — `1.3.3` with Node.js `20` alongside it (also spelled `bun-legacy`)
8+
- Bun `bun-1.4` — `1.4.4`, no Node.js binary
89

910
If you don't set a `runtime`, or you set `runtime: "node"`, your tasks run on the current Node.js LTS. That version moves forward as new LTS releases land, so set an explicit version — for example `runtime: "node-24"` — if you need it to stay fixed.
1011

1112
```ts
1213
import { defineConfig } from "@trigger.dev/sdk";
1314

1415
export default defineConfig({
15-
// "node", "node-22", "node-24", "node-26" or "bun"
16+
// "node", "node-22", "node-24", "node-26", "bun" or "bun-1.4"
1617
runtime: "node-24",
1718
project: "<your-project-ref>",
1819
});

‎packages/cli-v3/src/build/plugins.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as esbuild from "esbuild";
22
import { BuildTarget } from "@trigger.dev/core/v3/schemas";
3-
import { ResolvedConfig } from "@trigger.dev/core/v3/build";
3+
import { isBunRuntime, ResolvedConfig } from "@trigger.dev/core/v3/build";
44
import { configPlugin } from "../config.js";
55
import { logger } from "../utilities/logger.js";
66
import { bunPlugin } from "../runtimes/bun.js";
@@ -25,7 +25,7 @@ export async function buildPlugins(
2525

2626
plugins.push(polyshedPlugin());
2727

28-
if (resolvedConfig.runtime === "bun") {
28+
if (isBunRuntime(resolvedConfig.runtime)) {
2929
plugins.push(bunPlugin());
3030
}
3131

‎packages/cli-v3/src/commands/init.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Examples:
102102
)
103103
.option(
104104
"-r, --runtime <runtime>",
105-
"Which runtime to use for the project. Supported: node, node-22, node-24, node-26, bun",
105+
"Which runtime to use for the project. Supported: node, node-22, node-24, node-26, bun, bun-1.4",
106106
"node-24"
107107
)
108108
.option("--skip-package-install", "Skip installing the @trigger.dev/sdk package")

‎packages/cli-v3/src/config.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
} from "@trigger.dev/core/v3";
88
import type { ResolvedConfig } from "@trigger.dev/core/v3/build";
99
import {
10+
isBunRuntime,
1011
DEFAULT_RUNTIME,
1112
deprecatedRuntimeReplacement,
1213
isDeprecatedConfigRuntime,
@@ -367,10 +368,10 @@ function validateConfig(config: TriggerConfig, warn = true) {
367368
);
368369
}
369370

370-
if (config.runtime && config.runtime === "bun") {
371+
if (isBunRuntime(config.runtime)) {
371372
warn &&
372373
prettyWarning(
373-
`The "bun" runtime is currently experimental, and certain features may not work, especially opentelemetry instrumentation of 3rd party packages.`
374+
`The "${config.runtime}" runtime is currently experimental, and certain features may not work, especially opentelemetry instrumentation of 3rd party packages.`
374375
);
375376
}
376377
}

‎packages/cli-v3/src/deploy/buildImage.test.ts‎

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,18 @@ const images: Array<[BuildRuntime, string, string]> = [
5151
],
5252
[
5353
"bun",
54-
"triggerdotdev/bun:1.3-node20-bookworm@sha256:25b467196277b9d75a37773ee36d28b65ca81a6f41786f7d7d7f1fad95fb5a31",
55-
"triggerdotdev/bun:1.3-node20-bookworm-build@sha256:a2d5e6d1ec25946ca1d86abdd9ffb9c589376df64ee1b490c461607953931245",
54+
"triggerdotdev/bun:1.3-node20-bookworm@sha256:445d326788a0ba252e93c3923546fc06c44a5c303a0f476821f3f677864e160f",
55+
"triggerdotdev/bun:1.3-node20-bookworm-build@sha256:3fbcebfc0eb098f70d7b3d29e81b26f59ef1c21887703b85621e9cfe50ab7bce",
56+
],
57+
[
58+
"bun-legacy",
59+
"triggerdotdev/bun:1.3-node20-bookworm@sha256:445d326788a0ba252e93c3923546fc06c44a5c303a0f476821f3f677864e160f",
60+
"triggerdotdev/bun:1.3-node20-bookworm-build@sha256:3fbcebfc0eb098f70d7b3d29e81b26f59ef1c21887703b85621e9cfe50ab7bce",
61+
],
62+
[
63+
"bun-1.4",
64+
"triggerdotdev/bun:1.4-bookworm@sha256:91c471435cbf097e2301f17ec456cc398dc9ce9161d2317f4bbbd9cdae73a86e",
65+
"triggerdotdev/bun:1.4-bookworm-build@sha256:9b16175d5bd65d036beae9f04518c0b6c8327aa5d7855002188899fb6f9a7376",
5666
],
5767
];
5868

@@ -87,6 +97,55 @@ describe("generateContainerfile", () => {
8797
}
8898
);
8999

100+
it.each(["bun", "bun-legacy", "bun-1.4"] as BuildRuntime[])(
101+
"builds %s on a bun image with the bun user",
102+
async (runtime) => {
103+
const containerfile = await generateContainerfile({
104+
runtime,
105+
build: {},
106+
image: undefined,
107+
indexScript: "index.js",
108+
entrypoint: "entrypoint.js",
109+
});
110+
111+
expect(containerfile).toContain(`FROM ${BASE_IMAGE[runtime]} AS base`);
112+
expect(containerfile).toContain("USER bun");
113+
expect(containerfile).toContain("RUN bun install --production --no-save");
114+
expect(containerfile).toContain(`RUN [ "bun", "run", "index.js" ]`);
115+
}
116+
);
117+
118+
it.each(["bun", "bun-legacy"] as BuildRuntime[])(
119+
"keeps the run controller on node for the %s node + bun image",
120+
async (runtime) => {
121+
const containerfile = await generateContainerfile({
122+
runtime,
123+
build: {},
124+
image: undefined,
125+
indexScript: "index.js",
126+
entrypoint: "entrypoint.js",
127+
});
128+
129+
expect(containerfile).toContain(`ENTRYPOINT [ "dumb-init", "node", "entrypoint.js" ]`);
130+
}
131+
);
132+
133+
it.each(["bun-1.4"] as BuildRuntime[])(
134+
"runs the run controller under bun on the node-less %s image",
135+
async (runtime) => {
136+
const containerfile = await generateContainerfile({
137+
runtime,
138+
build: {},
139+
image: undefined,
140+
indexScript: "index.js",
141+
entrypoint: "entrypoint.js",
142+
});
143+
144+
expect(containerfile).toContain(`ENTRYPOINT [ "dumb-init", "bun", "entrypoint.js" ]`);
145+
expect(containerfile).not.toContain(`"dumb-init", "node"`);
146+
}
147+
);
148+
90149
it.each(["node", "bun"] as BuildRuntime[])(
91150
"runs no package installation for uncustomized projects on %s",
92151
async (runtime) => {
@@ -353,7 +412,7 @@ describe("generateContainerfile", () => {
353412
expect(repair).toBeGreaterThan(instructions);
354413
});
355414

356-
it.each(["node", "bun"] as BuildRuntime[])(
415+
it.each(["node", "bun-legacy", "bun-1.4"] as BuildRuntime[])(
357416
"splits node_modules and app code into separate layers for %s",
358417
async (runtime) => {
359418
const containerfile = await generateContainerfile({
@@ -364,7 +423,7 @@ describe("generateContainerfile", () => {
364423
entrypoint: "entrypoint.js",
365424
});
366425

367-
const user = runtime === "bun" ? "bun:bun" : "node:node";
426+
const user = runtime.startsWith("bun") ? "bun:bun" : "node:node";
368427

369428
expect(containerfile).toContain("FROM scratch AS code");
370429
expect(containerfile).toContain("COPY --from=build --exclude=node_modules /app /app");
@@ -377,7 +436,7 @@ describe("generateContainerfile", () => {
377436
}
378437
);
379438

380-
it.each(["node", "bun"] as BuildRuntime[])(
439+
it.each(["node", "bun-legacy"] as BuildRuntime[])(
381440
"orders post-install commands, the node_modules guard, and the code stage for %s",
382441
async (runtime) => {
383442
const containerfile = await generateContainerfile({

0 commit comments

Comments
 (0)