Last updated on 24th February 2026

Bun: The Complete Guide to the All-in-One JavaScript Runtime

Bun is an all-in-one JavaScript runtime built from the ground up to be fast. Written in Zig and using JavaScriptCore (the engine powering Safari) instead of V8, Bun combines a runtime, package manager, bundler, and test runner into a single executable. Where most JavaScript toolchains require you to wire together Node.js, npm or Yarn, esbuild or Webpack, and Jest or Vitest, Bun handles all of it natively — with benchmarks that consistently show 2-5x faster startup times and package installation speeds that leave npm in the dust. Since Bun 1.0's production-stable release and the continued improvements in 1.1+, it has become a serious choice for everything from small scripts to production web services.

Why Bun Matters for Web Developers

The JavaScript ecosystem has long suffered from toolchain sprawl — a typical project might involve five or more separate tools just to go from source code to running server. Each tool has its own configuration format, its own version to manage, and its own surface area for things to go wrong. Bun collapses this complexity. One binary handles the tasks that previously required an entire dependency tree of dev tooling, which means fewer things to install, fewer things to configure, and fewer things to debug when something breaks in CI.

Raw performance is the other headline. Bun's startup time is dramatically lower than Node.js because JavaScriptCore initialises faster than V8. For serverless functions, scripts, and CLI tools, this matters enormously — cold starts that took 300ms under Node can drop below 50ms with Bun. For bun install, the speed difference is almost embarrassing: a node_modules directory that takes 45 seconds to populate with npm often takes under five seconds with Bun, because it uses a global module cache and hard links rather than copying files.

Native TypeScript support means there is no compilation step to set up before running .ts files. Running bun run server.ts just works, with no ts-node, no tsx, no esbuild wrapper required. The same applies to JSX and TSX. Bun parses and transpiles these formats natively, using the same engine that handles JavaScript, which keeps the toolchain coherent and fast.

Node.js compatibility is a deliberate design goal rather than an afterthought. Bun implements the Node.js API surface — fs, path, http, crypto, child_process, stream, and most others — so the vast majority of npm packages work without modification. The bun: namespace exposes Bun-specific APIs for when you want to reach for native capabilities. This means you can migrate an existing Node.js project incrementally, running it under Bun before gradually adopting Bun-native patterns.

Step 1: System Requirements

Before installing Bun, confirm your environment meets the following requirements.

Supported operating systems:

  • macOS (x64 and Apple Silicon, macOS 12+)
  • Linux (x64 and ARM64, kernel 5.1+, glibc 2.17+)
  • Windows (x64, Windows 10+, experimental as of Bun 1.1)

For production servers, Linux is the primary target and receives the most optimisation work. If you are deploying to a VPS or bare metal server, a modern Ubuntu LTS (22.04 or 24.04) or Debian release is an excellent choice.

Git should already be installed if you are working with any version-controlled project. No other prerequisites are required — Bun ships as a single self-contained binary and brings its own JavaScript engine.

Step 2: Install Bun

macOS and Linux

The official install script downloads the Bun binary, verifies its checksum, and places it in ~/.bun/bin:

curl -fsSL https://bun.sh/install | bash

After the script completes, add Bun to your shell's PATH by following the instructions printed to your terminal. For bash:

echo 'export BUN_INSTALL="$HOME/.bun"' >> ~/.bashrc
echo 'export PATH="$BUN_INSTALL/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

For zsh, replace .bashrc with .zshrc.

Homebrew (macOS)

brew tap oven-sh/bun
brew install bun

npm (global install, any platform)

If you already have Node.js available and want a quick install without the curl script:

npm install -g bun

Windows

On Windows, use PowerShell:

powershell -c "irm bun.sh/install.ps1 | iex"

Note that Windows support is still marked experimental. For production workloads on Windows, running Bun inside WSL2 is the more reliable path.

Verify the installation

bun --version

Upgrading Bun

bun upgrade

Step 3: Project Setup

Initialising a new project

bun init creates a new project with a package.json, a basic tsconfig.json, and an entry file:

mkdir my-bun-project
cd my-bun-project
bun init

For a blank project with no prompts:

bun init -y

Package management

Installing all dependencies from an existing package.json:

bun install

Adding a production dependency:

bun add hono

Adding a development dependency:

bun add -d @types/bun

Running a script from package.json:

bun run build
bun run dev

The lockfile

Bun generates a bun.lockb binary lockfile rather than the text-based package-lock.json or yarn.lock. The binary format is faster to parse and smaller on disk. Commit bun.lockb to version control so that all team members and your CI environment get identical dependency trees.

TypeScript configuration

Bun generates a sensible tsconfig.json on bun init, but here is a solid starting point for a Bun-first project:

{
  "compilerOptions": {
    "lib": ["ESNext"],
    "module": "ESNext",
    "target": "ESNext",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "allowImportingTsExtensions": true,
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true,
    "noEmit": true,
    "strict": true,
    "skipLibCheck": true,
    "types": ["bun-types"]
  }
}

Install the type definitions for Bun's built-in APIs:

bun add -d bun-types

Step 4: Configure Bun for Your Workflow

bunfig.toml

bunfig.toml is Bun's configuration file, equivalent to .npmrc combined with parts of Vite's and Jest's config:

[install.scopes]
"@mycompany" = { registry = "https://npm.mycompany.internal", token = "$NPM_TOKEN" }

[install]
registry = "https://registry.npmjs.org"
production = false

[test]
coverage = true
coverageReporter = ["text", "lcov"]
coverageDir = "coverage"
preload = ["./tests/setup.ts"]

[bundle]
outdir = "dist"
target = "browser"

Defining scripts

package.json scripts work exactly as they do under npm:

{
  "scripts": {
    "dev": "bun run --hot src/index.ts",
    "build": "bun build src/index.ts --outdir dist --target node",
    "start": "bun run dist/index.js",
    "test": "bun test",
    "typecheck": "tsc --noEmit",
    "lint": "eslint src --ext .ts,.tsx"
  }
}

Workspaces

Bun supports npm-style workspaces for monorepos:

{
  "name": "my-monorepo",
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}

Running bun install from the root installs all workspace packages and symlinks them appropriately. To run a script in a specific workspace:

bun run --filter "./apps/web" dev

Step 5: Core Features

Runtime

The simplest use of Bun is as a drop-in replacement for node:

bun server.js
bun server.ts

Bun supports ES modules (import/export) and CommonJS (require) in the same project without configuration.

Package manager

bun install --frozen-lockfile
bun install --production
bun pm ls --all

Bundler

Bun's bundler produces optimised output for both browsers and Node.js-compatible servers:

bun build src/index.ts --outdir dist --target browser
bun build src/server.ts --outdir dist --target node
bun build src/index.ts --outdir dist --minify
bun build src/cli.ts --compile --outfile my-tool

The --compile flag bundles your code and Bun's runtime into a single binary.

You can also use the bundler programmatically:

const result = await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  target: "browser",
  minify: true,
  splitting: true,
});

if (!result.success) {
  console.error("Build failed:", result.logs);
  process.exit(1);
}

Test runner

Bun's built-in test runner uses the same API as Jest:

import { expect, test, describe, beforeEach, afterEach } from "bun:test";

describe("user authentication", () => {
  test("hashes passwords before storing", async () => {
    const hash = await Bun.password.hash("hunter2");
    expect(hash).not.toBe("hunter2");
    expect(await Bun.password.verify("hunter2", hash)).toBe(true);
  });
});

Run all tests:

bun test
bun test --watch
bun test --coverage

Building with Bun? DeployHQ connects your Git repo to any server and deploys automatically when you push — SFTP, SSH, or cloud. Try it free.


Step 6: Advanced Features

Bun.serve — HTTP server

Bun.serve() is Bun's built-in HTTP server, implemented in native code:

const server = Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);

    if (url.pathname === "/") {
      return new Response("Hello from Bun!", {
        headers: { "Content-Type": "text/plain" },
      });
    }

    if (url.pathname === "/health") {
      return Response.json({ status: "ok", uptime: process.uptime() });
    }

    return new Response("Not Found", { status: 404 });
  },
  error(err) {
    console.error(err);
    return new Response("Internal Server Error", { status: 500 });
  },
});

console.log(`Listening on http://localhost:${server.port}`);

Bun.serve() also supports WebSockets natively:

Bun.serve({
  port: 3000,
  fetch(req, server) {
    if (server.upgrade(req)) {
      return;
    }
    return new Response("Upgrade required", { status: 426 });
  },
  websocket: {
    open(ws) { console.log("Client connected"); },
    message(ws, message) { ws.send(`Echo: ${message}`); },
    close(ws) { console.log("Client disconnected"); },
  },
});

Built-in SQLite

Bun ships with SQLite as a first-class built-in:

import { Database } from "bun:sqlite";

const db = new Database("myapp.sqlite");

db.exec(`
  CREATE TABLE IF NOT EXISTS posts (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    body TEXT,
    created_at INTEGER DEFAULT (unixepoch())
  )
`);

const insert = db.prepare(
  "INSERT INTO posts (title, body) VALUES ($title, $body)"
);

insert.run({ $title: "My First Post", $body: "Hello, world!" });

const posts = db
  .query("SELECT * FROM posts ORDER BY created_at DESC LIMIT ?")
  .all(10);

db.close();

Foreign Function Interface (FFI)

Bun can call native code from shared libraries:

import { dlopen, FFIType, suffix } from "bun:ffi";

const { symbols } = dlopen(`libm.${suffix}`, {
  sqrt: {
    args: [FFIType.double],
    returns: FFIType.double,
  },
});

console.log(symbols.sqrt(144)); // 12

Hot reloading

bun run --watch src/index.ts    # restarts on change
bun run --hot src/index.ts      # reloads modules without restart

Step 7: Best Practices

When to use Bun vs Node.js

Use Bun when:

  • Starting a new project and want a fast, integrated toolchain
  • Startup time matters (CLI tools, serverless functions, scripts)
  • You want native TypeScript without a separate compilation step
  • You need built-in SQLite without a native addon

Keep Node.js when:

  • You depend on packages with Node-specific native addons
  • You are in a locked-down CI environment where Bun cannot be installed
  • Migration overhead is not justified for the team

Commit the lockfile

Always commit bun.lockb. Use --frozen-lockfile in CI for deterministic installs.

Use Bun.$ for shell scripting

import { $ } from "bun";

const output = await $`ls -la`.text();
const dir = "dist";
await $`rm -rf ${dir} && mkdir ${dir}`;

Step 8: Deploy with DeployHQ

Install Bun on your target server

curl -fsSL https://bun.sh/install | bash
source ~/.bashrc
bun --version

Ensure Bun's binary path is in the system PATH for the deploy user:

export BUN_INSTALL="/home/deploy/.bun"
export PATH="$BUN_INSTALL/bin:$PATH"

Configure your DeployHQ build commands

bun install --frozen-lockfile
bun run build

Serving with Bun.serve

Create an ecosystem.config.js:

module.exports = {
  apps: [
    {
      name: "my-bun-app",
      script: "bun",
      args: "run start",
      interpreter: "none",
      env: {
        NODE_ENV: "production",
        PORT: "3000",
      },
    },
  ],
};

Post-deployment command:

pm2 reload ecosystem.config.js --env production

Alternatively, use systemd:

[Unit]
Description=My Bun Application
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/var/www/my-bun-app
ExecStart=/home/deploy/.bun/bin/bun run start
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=3000

[Install]
WantedBy=multi-user.target

Branch environments

  • main branch -> production server, NODE_ENV=production
  • develop branch -> staging server, NODE_ENV=staging

For production, always use --frozen-lockfile:

bun install --frozen-lockfile
NODE_ENV=production bun run build

Step 9: Troubleshooting

bun: command not found after installation

Source your shell config:

source ~/.bashrc

Verify ~/.bun/bin is in your PATH.

bun install fails with lockfile had changes

Run bun install locally without --frozen-lockfile, commit both package.json and bun.lockb, then push.

A package fails to install or import

Some packages with native addons compile specifically for Node.js. Common replacements:

  • better-sqlite3 -> bun:sqlite (built-in)
  • bcrypt -> Bun.password (built-in)

TypeScript errors about Bun globals

Ensure bun-types is installed and in your tsconfig.json:

{
  "compilerOptions": {
    "types": ["bun-types"]
  }
}

Port already in use on deployment

pm2 restart my-bun-app
# or
lsof -i :3000

Conclusion

Bun represents a significant shift in what a JavaScript runtime can be. By collapsing the runtime, package manager, bundler, and test runner into a single fast binary — with native TypeScript support and strong Node.js compatibility — it removes a layer of toolchain complexity that has been a constant background cost of JavaScript development for years. Whether you adopt it as a full replacement for your Node.js stack or use it incrementally for its package installation speed alone, Bun rewards the switch with faster iteration cycles and simpler project configuration.

For production deployments, the combination of bun install --frozen-lockfile, bun run build, and a simple process manager like PM2 or systemd gives you a reliable, reproducible deployment pipeline. Pair that with DeployHQ's automatic Git-triggered deployments and you have a workflow that goes from git push to live update with minimal manual intervention.


If you have questions about deploying your Bun projects, reach out to us at support@deployhq.com or find us on Twitter/X.