## What it is

Bun is an all-in-one JavaScript runtime, package manager, bundler, and test runner — `bun install` replaces `npm install`, `bun run server.ts` replaces `node` + `tsx`, `bun build` replaces webpack/esbuild, and `bun test` replaces Jest. It's positioned as the fastest tool in each of those roles, and at this point it's mature enough to run production workloads.

This sheet is the command-recall companion to the deeper [Bun runtime guide](https://www.deployhq.com/guides/bun) — the install one-liners you need without reading prose, the daily-use commands grouped by phase (project init → install → run → build → test → deploy), and the lockfile + Docker patterns that hold up under CI. For the why-and-how of Bun's architecture, Node.js compatibility, and runtime APIs, the guide goes deeper.

## Quick reference

### Install

```bash
# macOS, Linux, WSL
curl -fsSL https://bun.sh/install | bash

# macOS with Homebrew
brew install oven-sh/bun/bun

# npm (works anywhere npm does — useful in CI runners that already have Node)
npm install -g bun

# Docker / CI image
# use the official image: oven/bun:1 (or pin: oven/bun:1.1.34-alpine)
```

```powershell
# Windows (PowerShell)
powershell -c "irm bun.sh/install.ps1 | iex"

# Windows with Scoop
scoop install bun
```

Verify the install and confirm you're on a recent version:

```bash
bun --version            # e.g. 1.1.34
bun --revision           # commit + build date
which bun                # confirm PATH resolution
```

If `bun: command not found` after install, the installer added `~/.bun/bin` to your shell rc but the current shell hasn't sourced it — open a new shell, or `source ~/.bashrc` (or `~/.zshrc`, or whichever `bun install` wrote to).

### Update and pin

```bash
bun upgrade                          # latest stable
bun upgrade --canary                 # latest canary build
bun upgrade --version 1.1.30         # pin to a specific version

# In CI / Dockerfile — never use `latest`; pin the image tag instead
# FROM oven/bun:1.1.34-alpine
```

The `bun upgrade` command self-updates the binary in `~/.bun/bin/bun`. On macOS systems that installed via Homebrew, use `brew upgrade bun` instead — `bun upgrade` would diverge from Homebrew's bookkeeping.

### Project init

```bash
bun init                             # interactive scaffold
bun init -y                          # accept all defaults (TS + minimal)
bun init --minimal                   # bare-bones, no example files

bun create react ./my-app            # scaffold from a Bun template
bun create vite ./my-app             # any `create-*` npm package works
bun create elysia ./my-api           # Bun-native HTTP framework
```

`bun init` produces `package.json`, `tsconfig.json`, `index.ts`, `.gitignore`, and a `bun.lock`. No `node_modules` until you `bun install`.

### Install packages

```bash
bun install                          # install everything in package.json
bun install --frozen-lockfile        # fail if bun.lock doesn't match — use in CI
bun install --production             # skip devDependencies — use in Docker prod stage
bun install --no-save                # install without writing to package.json

bun add react react-dom              # add dependencies
bun add -d typescript @types/node    # add devDependencies (-D also works)
bun add -g typescript                # global install
bun add github:facebook/react        # install directly from a GitHub repo
bun add jsr:@std/path                # JSR registry package

bun remove lodash                    # uninstall + remove from package.json
bun update                           # update all packages within semver
bun update --latest react            # update one package across major versions
bun outdated                         # show outdated packages without installing
```

Bun installs are dramatically faster than npm because the binary cache lives in `~/.bun/install/cache/` and is content-addressable — every project shares the same downloaded tarballs.

### Workspaces (monorepos)

```jsonc
// package.json (root)
{
  "name": "monorepo",
  "private": true,
  "workspaces": ["packages/*", "apps/*"]
}
```

```bash
bun install                                    # installs every workspace's deps
bun add lodash --filter @scope/some-package    # add to one specific workspace
bun run --filter '*' build                     # run "build" in every workspace
bun run --filter './apps/*' test               # filter by glob path
```

`--filter` accepts package names, workspace names, or path globs — the same syntax pnpm uses.

### Run scripts and files

```bash
bun run dev                          # run a package.json script
bun dev                              # same — `run` is optional for scripts
bun run --filter '*' test            # run "test" in every workspace

bun run server.ts                    # execute a TS/JS file directly (no tsx/ts-node)
bun server.ts                        # same — `run` is optional for files
bun --hot server.ts                  # hot-reload on file change (dev server pattern)
bun --watch server.ts                # restart on change (more aggressive than --hot)

bun x cowsay "hello"                 # ad-hoc execute a package (like npx)
bunx cowsay "hello"                  # alias for `bun x`
```

`bun --hot` is the daily-use server-side flag — it preserves application state between reloads (database connections, in-memory caches), which `--watch` doesn't. Use `--hot` for dev servers; `--watch` for scripts that need a fresh process on every change.

### Build (bundler)

```bash
bun build ./src/index.ts --outdir ./dist
bun build ./src/index.ts --outfile ./dist/bundle.js
bun build ./src/index.ts --outdir ./dist --target browser
bun build ./src/index.ts --outdir ./dist --target node     # produce a Node-compat bundle
bun build ./src/index.ts --outdir ./dist --target bun      # default — uses Bun APIs

bun build ./src/index.ts --outdir ./dist --minify
bun build ./src/index.ts --outdir ./dist --sourcemap
bun build ./src/index.ts --outdir ./dist --external react  # exclude from bundle

# Single-file executable — ships the runtime + your code in one binary
bun build ./src/cli.ts --compile --outfile ./dist/mycli
bun build ./src/cli.ts --compile --target=bun-linux-x64 --outfile ./dist/mycli-linux
```

`--compile` is unique to Bun — it produces a self-contained native executable (no `bun` binary required on the target machine). Useful for distributing CLI tools and serverless cold-start optimisation.

### Test runner

```bash
bun test                             # run all *.test.ts / *.spec.ts files
bun test ./path/to/file.test.ts      # run a single file
bun test --watch                     # re-run on file change
bun test --coverage                  # text coverage report
bun test --coverage --coverage-reporter=lcov  # lcov for CI tools (Coveralls, Codecov)
bun test --bail                      # stop on first failure
bun test --timeout 10000             # per-test timeout in ms (default 5000)

bun test -t "login flow"             # filter tests by name pattern
bun test --update-snapshots          # rewrite snapshot expectations
```

The test API is Jest-compatible (`describe`, `it`, `expect`, `beforeEach`, etc.) so existing Jest test files often run unmodified. The big practical difference: `bun test` starts in ~20ms vs Jest's 1-3 seconds, which changes how you run tests during development (more often, smaller scopes).

### Environment variables and `.env` files

```bash
# Bun auto-loads .env files — no dotenv package needed
bun server.ts                        # reads .env, .env.local, .env.${NODE_ENV}

# Override per-invocation
NODE_ENV=production bun server.ts

# Specify a different file
bun --env-file=.env.staging server.ts
```

```typescript
// In code — typed access to env vars
const port = Bun.env.PORT || "3000";
const dbUrl = process.env.DATABASE_URL;   // also works (Node compat)
```

Bun loads `.env` files in this order (later files override earlier ones): `.env`, `.env.local`, `.env.${NODE_ENV}`, `.env.${NODE_ENV}.local`. The `.local` variants should be in `.gitignore`.

### Lockfile

```bash
bun install                          # generates/updates bun.lock
bun install --frozen-lockfile        # CI mode — fails if lockfile is out of sync
bun install --lockfile-only          # update lockfile, skip node_modules

# Convert from another package manager
bun install --yarn                   # generate yarn.lock alongside bun.lock
```

`bun.lock` is text-based (not binary) and intended to be reviewed in pull requests. Older Bun versions used `bun.lockb` (binary) — they're forward-compatible, but if you're starting a new project today, you'll see `bun.lock`.

**Commit `bun.lock` to git** unless you have a specific reason not to. CI builds should run `bun install --frozen-lockfile` so a stale lockfile breaks the build instead of silently picking a different package version.

### Bun shell

```typescript
// Cross-platform shell scripting from inside Bun
import { $ } from "bun";

const branch = (await $`git rev-parse --abbrev-ref HEAD`.text()).trim();
const files = await $`ls -la`.text();

// Pipes, env vars, all standard shell behaviour
await $`echo $HOME > /tmp/home.txt`;
await $`cat package.json | jq '.dependencies'`;

// Glob expansion happens in Bun's parser — portable across macOS/Linux/Windows
const ts_files = await $`ls src/*.ts`.text();
```

`Bun.$` is portable shell scripting that works the same on macOS, Linux, and Windows — useful for build scripts that historically needed `cross-env` and `rimraf` to be cross-platform.

---

## Deployment workflows (the moat)

### 1. Lock-file discipline in CI

The single most important Bun deploy pattern: every CI run must install with `--frozen-lockfile` so a stale lockfile fails the build instead of silently changing package versions.

```yaml
# .github/workflows/ci.yml (or any equivalent CI config)
- uses: oven-sh/setup-bun@v2
  with:
    bun-version: 1.1.34

- run: bun install --frozen-lockfile
- run: bun test
- run: bun build ./src/index.ts --outdir ./dist
```

For DeployHQ build pipelines, the same flag goes in the build configuration — the [build pipelines](https://www.deployhq.com/features/build-pipelines) doc covers the shape; the Bun-specific commands are above.

Pin the Bun version (`bun-version: 1.1.34`) rather than `latest`. A Bun update can introduce a behaviour change between releases, and a deploy isn't where you want to discover it.

### 2. Multi-stage Docker build

The production-ready Bun Dockerfile pattern: build in one stage with full dev dependencies, copy the artifact into a slim runtime stage with production deps only.

```dockerfile
# syntax=docker/dockerfile:1.7

# --- build stage ---
FROM oven/bun:1.1.34-alpine AS build
WORKDIR /app

# Cache the dep layer separately from source for fast rebuilds
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

COPY . .
RUN bun run build      # whatever your build script does — bun build, vite build, etc.

# --- production stage ---
FROM oven/bun:1.1.34-alpine
WORKDIR /app

ENV NODE_ENV=production

# Production deps only (skips ./node_modules from the build stage)
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile --production

# Copy only the build artifact + anything else the runtime needs
COPY --from=build /app/dist ./dist

USER bun                # non-root user shipped with the image
EXPOSE 3000
CMD ["bun", "run", "dist/server.js"]
```

The `--production` flag in the runtime stage strips devDependencies, which typically halves image size. The `USER bun` line uses the non-root user the official image ships with — required for any image that'll be scanned by a security tool.

For a static-output build (Vite, Next.js export, Astro), the runtime stage can skip Bun entirely and just `COPY --from=build /app/dist /usr/share/nginx/html` into an nginx image.

### 3. Bun + DeployHQ build pipeline

DeployHQ's build pipeline runs your build commands inside a container before the deploy happens. The container picks up `bun` automatically if `bun.lock` is present in the repository, per the [Using Bun with DeployHQ Build](https://www.deployhq.com/support/using-bun-with-deployhq-build) integration. The build commands look the same as the CI block above:

```bash
bun install --frozen-lockfile
bun run build
bun test
```

The artifact produced by `bun run build` is what DeployHQ uploads to the target servers — your runtime servers don't need Bun installed at all unless the application itself is running under Bun. See the [Bun support article](https://www.deployhq.com/support/bun) for the configuration steps.

For the wider question of which JavaScript runtime to standardise on, the [npm vs yarn vs pnpm vs bun comparison](https://www.deployhq.com/blog/choosing-the-right-package-manager-npm-vs-yarn-vs-pnpm-vs-bun) walks through the tradeoffs.

### 4. Compile to a single-file binary

The `--compile` flag produces a self-contained native executable — useful for CLI tools, edge-deployed functions, and any case where installing Bun on the target machine is friction.

```bash
# Build for the current platform
bun build ./src/cli.ts --compile --outfile ./bin/mycli

# Cross-compile (no Docker required)
bun build ./src/cli.ts --compile --target=bun-linux-x64 --outfile ./bin/mycli-linux
bun build ./src/cli.ts --compile --target=bun-linux-arm64 --outfile ./bin/mycli-arm64
bun build ./src/cli.ts --compile --target=bun-darwin-arm64 --outfile ./bin/mycli-mac-arm
bun build ./src/cli.ts --compile --target=bun-windows-x64 --outfile ./bin/mycli.exe
```

The resulting binaries are ~50-100 MB (they include the Bun runtime), but they start in single-digit milliseconds and have no external dependencies. For a [DeployHQ CLI](https://www.deployhq.com/agents)-style distribution model, this is the cleanest way to ship a TypeScript tool to users who don't have Bun installed.

### 5. Hot reload for dev, restart for workers

Two reload modes serve different deploy-adjacent workflows:

```bash
bun --hot server.ts        # dev server — preserves DB connections, in-memory state
bun --watch worker.ts      # background worker — full restart on change, clean slate
```

`--hot` is the right default for HTTP servers because it keeps `Bun.serve()`'s socket bound across reloads — no port flapping, no losing in-flight requests. `--watch` is the right default for queue workers because state from a partial run shouldn't carry into the next run.

For local development with a `package.json` dev script:

```json
{
  "scripts": {
    "dev": "bun --hot src/server.ts",
    "worker": "bun --watch src/worker.ts"
  }
}
```

---

## Common errors and fixes

| Error / symptom | Cause | Fix |
|---|---|---|
| `bun: command not found` after install | Installer wrote to `~/.bashrc` / `~/.zshrc` but current shell hasn't sourced it | Open a new shell or `source ~/.bashrc` (or whichever file the installer modified) |
| `error: failed to fetch package: bun.lock has been updated` | CI lockfile is out of sync with `package.json` | Run `bun install` locally, commit the updated `bun.lock`, re-trigger CI |
| `Cannot find module 'X'` after `bun install` | Package has an optional native binary that didn't install (e.g. `sharp`, `better-sqlite3`) | `bun install --force` to re-resolve, or add the `--cpu`/`--os` overrides to `package.json`'s `bun.install.overrides` |
| `bun upgrade` reports "already on the latest version" but `--version` shows old | macOS Homebrew install — `bun upgrade` doesn't manage that binary | `brew upgrade bun` instead |
| `EACCES: permission denied` on global install | `~/.bun` owned by root from a previous `sudo` install | `sudo chown -R $(whoami) ~/.bun` |
| `Module not found: 'fs'` (or `path`, `crypto`, etc.) | Browser build target with Node imports in code | Set `--target node` or `--target bun`, or move the Node-specific code behind a runtime check |
| `bun test` hangs without output | A test imported a module that opens a server/timer and didn't close it | Add `--bail` to fail fast; ensure `afterAll` cleanup runs |
| `bun --hot` not reloading on file change | Watching a symlink or a `node_modules`-shadowed path | Add the file path to `bun --hot --watch=true` or restart with `--watch` |
| `bun build --compile` binary won't run on the deploy target | Wrong cross-compile target | Match `--target=bun-{platform}-{arch}` to the deploy host's OS/arch |
| `error: Couldn't find or load Bun's lockfile` | `bun install --frozen-lockfile` in a fresh checkout where `bun.lock` isn't tracked | Commit `bun.lock` to the repository |
| `bun.lockb` vs `bun.lock` confusion | Old binary lockfile from Bun \<1.1 | Delete `bun.lockb` and run `bun install` — Bun writes the new text-based `bun.lock` |
| Different `bun.lock` on different machines | Different Bun versions producing slightly different resolutions | Pin Bun in CI (`oven-sh/setup-bun@v2 bun-version: X.Y.Z`) and document the version in `package.json` `engines` |
| `bun add` rewriting `package.json` formatting | Bun normalizes JSON — strips indentation/trailing-newline differences | Use `--no-save` for one-offs; otherwise accept the normalised format as the project standard |

---

## Companion: full DeployHQ deploy workflow

The end-to-end Bun deploy pattern through DeployHQ: push to a Git branch → DeployHQ webhook fires → [build pipeline](https://www.deployhq.com/features/build-pipelines) runs `bun install --frozen-lockfile && bun run build && bun test` inside a container → the build artifact is uploaded to the target servers → optional post-deploy hooks (run migrations, flush caches, restart the process manager).

For applications that run under Bun in production (HTTP server, queue worker), the deploy target needs Bun installed — the [Using Bun with DeployHQ Build](https://www.deployhq.com/support/using-bun-with-deployhq-build) integration handles the build-time side, and the [Bun support article](https://www.deployhq.com/support/bun) covers the runtime-side configuration. For static-output builds (Vite, Astro), the target just needs nginx or any static-file server — Bun is build-only.

If a release does ship a regression, [one-click rollback](https://www.deployhq.com/features/one-click-rollback) restores the previous build directory and process artifacts without re-running the pipeline.

[Start a free DeployHQ trial](https://www.deployhq.com/signup) to wire Bun into a production deploy pipeline.

---

## Related cheatsheets

- [Composer cheatsheet](https://www.deployhq.com/cheatsheets/composer) — for projects mixing Bun (frontend) with a PHP backend.
- [Docker cheatsheet](https://www.deployhq.com/cheatsheets/docker) — for the multi-stage Bun Dockerfile pattern in workflow #2.
- [Bash cheatsheet](https://www.deployhq.com/cheatsheets/bash) — for the `set -euo pipefail` patterns around CI scripts.
- [SSH cheatsheet](https://www.deployhq.com/cheatsheets/ssh) — for the deploy keys that authenticate CI to the runtime host.
- [Cron and Crontab cheatsheet](https://www.deployhq.com/cheatsheets/cron) — for scheduling Bun-based jobs in production.
- [Cheatsheets hub](https://www.deployhq.com/cheatsheets) — every DeployHQ cheatsheet in one place.

---

Need help? Email [support@deployhq.com](mailto:support@deployhq.com) or follow [@deployhq on X](https://x.com/deployhq).
