Test, build, and ship
Compile and ship the application
Compile the Bun application into one executable and review configuration, data, platform, and recovery before release.
A bundle still needs Bun installed on the server. Bun can go one step further and combine your application and the Bun runtime into a single executable. Copy that one file to a machine and it runs, with no Bun, no Node, and no node_modules on the other side.
Compile the notes API:
bun build \
--compile \
--outfile=notes-api \
index.ts
Run it without the bun command:
./notes-api
It prints Listening on http://localhost:3000/, like before. The file is large, tens of megabytes, because the whole runtime is inside it. That’s the trade: a big file in exchange for zero setup on the target machine.
Build for the target platform
An executable is platform-specific. The one you just built runs on your machine’s operating system and CPU architecture, and nowhere else. Copy a macOS ARM64 binary to a Linux server and you get cannot execute binary file: Exec format error.
The safest path is to build on the same operating system and architecture used for deployment, for example in a CI job running on Linux. Bun also supports cross-compilation with an explicit target when you need it:
bun build \
--compile \
--target=bun-linux-x64 \
--outfile=notes-api \
index.ts
Now the output runs on a 64-bit Linux server, even though you built it on a Mac.
Keep configuration and data outside
Do not embed production secrets into the executable. A compiled binary is not encrypted, and anyone with the file can read strings out of it. Supply configuration through environment variables at runtime, the same PORT and DATABASE_PATH we’ve used all along.
Keep notes.sqlite on durable storage, outside the directory you replace on deploy. Swapping the executable must never swap the database. Point DATABASE_PATH at a location that survives deployments, and back that file up.
Review the release
Before shipping, run the same sequence every time:
bun run typecheck
bun test
bun build --compile --outfile=notes-api index.ts
Then verify, on the target machine or a copy of it:
- the executable starts with production configuration
/healthreturns200- the notes database is writable and backed up
- logs do not contain secrets or complete request bodies
- the previous executable is still available for rollback
That last point is the one people forget. Keep the old binary next to the new one. If the new release misbehaves, rolling back is renaming a file, not rebuilding under pressure.
My advice is to keep the first deployment boring. One known Bun version, one tested artifact, explicit configuration, and a recovery path. Add clever things later, once the boring version has run for a while.
You now have the complete Bun workflow: run TypeScript, manage packages, use the Bun APIs, build an HTTP service, persist data with SQLite, test it, and ship it as a single file.
Lesson completed