Migrate from Node.js
Move an existing Node.js project to Bun in reversible, verifiable stages
Last updated on
Prefer parallel adoption
Do not replace the runtime, package manager, test runner, and bundler in one commit. Each additional boundary makes failures harder to attribute.
| Stage | Change | Pass condition | Rollback |
|---|---|---|---|
| 1. Local scripts | Run standalone tooling with Bun | Output matches Node | Keep node script.js |
| 2. Package manager | Generate bun.lock | Frozen install, tests, and build pass | Keep old lockfile through review |
| 3. Tests | Move one unit-test group | Results and coverage are acceptable | Old runner remains available |
| 4. Dev runtime | Run local service with Bun | Integration suite passes | Preserve old dev script |
| 5. Production | Change image or entrypoint | Staging load and rollback drills pass | Restore previous image |
Migration progress checklist
Progress stays in this browser and is never uploaded.
Inventory before editing
rg "node:|process\.|__dirname|require\(|worker_threads|child_process" src
bun pm lsPay special attention to native .node addons, custom ESM loaders or require hooks, Node/V8-specific diagnostics, Jest environments and transformers, framework support, and the target deployment platform.
Package-manager migration
bun install
bun install --frozen-lockfileWhen no bun.lock exists, Bun can migrate an existing npm, Yarn, or pnpm lockfile. Review the result and run the whole pipeline before deleting anything. Once approved, retain one authoritative lockfile.
Docker baseline
FROM oven/bun:1 AS install
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
FROM oven/bun:1 AS release
WORKDIR /app
COPY --from=install /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
USER bun
CMD ["bun", "run", "start"]Use .dockerignore, copy framework artifacts deliberately, and pin an organization-approved image version or digest.
Acceptance matrix
Verify type checking, unit/integration/end-to-end tests, cold start, throughput, memory, long-running stability, SIGTERM shutdown, timezones, Unicode, filesystem behavior, TLS and proxies, database pools, DNS, container architecture/libc, observability agents, and sourcemaps.
Add dual-runtime compatibility CI
During migration, execute the same portable business and integration tests under Node and Bun so differences become observable evidence:
strategy:
matrix:
runtime: [node, bun]The real workflow should use the organization's supported Node release and original package-manager path in the Node job, and a validated pinned Bun release in the Bun job. Compare exit status, HTTP contracts, database behavior, and artifacts—not identical internal logs or benchmark numbers. Remove the Node rollback path only after Bun passes every release gate.
Compatibility is an observed result
“Node.js-compatible” is a migration starting point, not a project acceptance result. Your dependency graph, framework version, and production platform decide whether the switch is safe.