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.

StageChangePass conditionRollback
1. Local scriptsRun standalone tooling with BunOutput matches NodeKeep node script.js
2. Package managerGenerate bun.lockFrozen install, tests, and build passKeep old lockfile through review
3. TestsMove one unit-test groupResults and coverage are acceptableOld runner remains available
4. Dev runtimeRun local service with BunIntegration suite passesPreserve old dev script
5. ProductionChange image or entrypointStaging load and rollback drills passRestore previous image

Migration progress checklist

0 / 5 done

Progress stays in this browser and is never uploaded.

Inventory before editing

rg "node:|process\.|__dirname|require\(|worker_threads|child_process" src
bun pm ls

Pay 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-lockfile

When 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.