Introducing Bun as a Runtime for Pulumi

Last year we added support for Bun as a package manager for Pulumi TypeScript projects. Today we’re taking the next step: Bun is now a fully supported runtime for Pulumi programs. Set runtime: bun in your Pulumi.yaml and Bun will execute your entire Pulumi program, with no Node.js required. Since Bun’s 1.0 release, this has been one of our most requested features.
Why Bun?
Bun is a JavaScript runtime designed as an all-in-one toolkit: runtime, package manager, bundler, and test runner. For Pulumi users, the most relevant advantages are:
- Native TypeScript support: Bun runs TypeScript directly without requiring ts-node or a separate compile step.
- Fast package management: Bun’s built-in package manager can install dependencies significantly faster than npm.
- Node.js compatibility: Bun aims for 100% Node.js compatibility, so the npm packages you already use with Pulumi should work out of the box.
With runtime: bun, Pulumi uses Bun for both running your program and managing your packages, giving you a streamlined single-tool experience.
Getting started
To create a new Pulumi project with the Bun runtime, run:
pulumi new bun
This creates a TypeScript project configured to use Bun. The generated Pulumi.yaml looks like this:
name: my-bun-project
runtime: bun
From here, write your Pulumi program as usual. For example, to create a random password using the @pulumi/random package:
bun add @pulumi/random
import * as random from "@pulumi/random";
const password = new random.RandomPassword("password", {
length: 20,
});
export const pw = password.result;
Then deploy with:
pulumi up
Prerequisites:
Converting existing Node.js projects
If you have an existing Pulumi TypeScript project running on Node.js, you can convert it to use the Bun runtime in a few steps.
1. Update Pulumi.yaml
Change the runtime field from nodejs to bun:
Before:
runtime:
name: nodejs
options:
packagemanager: npm
After:
runtime: bun
bun, Bun is also used as the package manager — there’s no need to configure a separate packagemanager option.2. Update tsconfig.json
Bun handles TypeScript differently from Node.js with ts-node. Update your tsconfig.json to use Bun’s recommended compiler options:
{
"compilerOptions": {
"lib": ["ESNext"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"moduleResolution": "bundler",
"allowJs": true,
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true
}
}
Key differences from a typical Node.js tsconfig.json:
module: "Preserve"andmoduleResolution: "bundler": Let Bun handle module resolution instead of compiling to CommonJS. Thebundlerresolution strategy allows extensionless imports while still respectingpackage.jsonexports, matching how Bun resolves modules in practice.verbatimModuleSyntax: true: Enforces consistent use of ESMimport/exportsyntax. TypeScript will flag any remaining CommonJS patterns likerequire()at compile time.
3. Switch to ESM
Bun makes it easy to go full ESM and it’s the recommended module format for Bun projects. Add "type": "module" to your package.json:
{
"type": "module"
}
With ECMAScript module (ESM) syntax, one thing that gets easier is working with async code. In a CommonJS Pulumi program, if you need to await a data source or other async call before declaring resources, the program must be wrapped in an async entrypoint function. With ESM and Bun, top-level await just works, so you can skip the wrapper function entirely and await directly at the module level:
import * as aws from "@pulumi/aws";
const azs = await aws.getAvailabilityZones({ state: "available" });
const buckets = azs.names.map(az => new aws.s3.BucketV2(`my-bucket-${az}`));
export const bucketNames = buckets.map(b => b.id);
If your existing program does use an async entrypoint with export =, just replace it with the ESM-standard export default:
// CommonJS (Node.js default)
export = async () => {
const bucket = new aws.s3.BucketV2("my-bucket");
return { bucketName: bucket.id };
};
// ESM (used with Bun)
export default async () => {
const bucket = new aws.s3.BucketV2("my-bucket");
return { bucketName: bucket.id };
};
4. Update the Pulumi SDK
Make sure you’re running @pulumi/pulumi version 3.226.0 or later:
bun add @pulumi/pulumi@latest
5. Install dependencies and deploy
pulumi install
pulumi up
Bun as runtime vs. Bun as package manager
With this release, there are now two ways to use Bun with Pulumi:
| Configuration | Bun’s role | Node.js required? |
|---|---|---|
runtime: bun | Runs your program and manages packages | No |
runtime: { name: nodejs, options: { packagemanager: bun } } | Manages packages only | Yes |
Use runtime: bun for the full Bun experience. The package-manager-only mode is still available for projects that need Node.js-specific features like function serialization.
Known limitations
The following Pulumi features are not currently supported when using the Bun runtime:
- Callback functions (magic lambdas) are not supported. APIs like
aws.lambda.CallbackFunctionand event handler shortcuts (e.g.,bucket.onObjectCreated) use function serialization which requires Node.jsv8andinspectormodules that are only partially supported in Bun. - Dynamic providers are not supported. Dynamic providers (
pulumi.dynamic.Resource) similarly rely on function serialization.
If your project uses any of these features, continue using runtime: nodejs. You can still benefit from Bun’s fast package management by setting packagemanager: bun in your runtime options.
Start using Bun with Pulumi
Bun runtime support is available now in Pulumi 3.227.0. To get started:
- Create a new project:
pulumi new bun - Read the docs: TypeScript (Node.js) SDK
- Report issues or share feedback on GitHub or in the Pulumi Community Slack
Thank you to everyone who upvoted, commented on, and contributed to the original feature request. Your feedback helped shape this feature, and we’d love to hear how it works for you.