Introducing Bun as a Runtime for Pulumi

Posted on
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
When the runtime is set to 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" and moduleResolution: "bundler": Let Bun handle module resolution instead of compiling to CommonJS. The bundler resolution strategy allows extensionless imports while still respecting package.json exports, matching how Bun resolves modules in practice.
  • verbatimModuleSyntax: true: Enforces consistent use of ESM import/export syntax. TypeScript will flag any remaining CommonJS patterns like require() 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:

ConfigurationBun’s roleNode.js required?
runtime: bunRuns your program and manages packagesNo
runtime: { name: nodejs, options: { packagemanager: bun } }Manages packages onlyYes

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:

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:

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.