Node.js, part 21: single executable applications — shipping a binary without Node
Part 21from the Node.js series · 24 parts in all
"Install Node 20 first" is a fine prerequisite for a developer tool and an impossible one for a colleague in finance, a CI image you do not control, or an air-gapped machine. Single executable applications (SEA) let you embed a bundled script into a copy of the Node binary itself. Part 21 is the pipeline and the honest limits.
The three steps
# 1. bundle to one CommonJS file (your bundler's job, not Node's)
npx esbuild cli.js --bundle --platform=node --outfile=dist/bundle.cjs
# 2. describe the injection
{
"main": "dist/bundle.cjs",
"output": "dist/sea-prep.blob",
"disableExperimentalSEAWarning": true
}
node --experimental-sea-config sea-config.json
# 3. copy the node binary, inject the blob, sign where the OS requires it
cp "$(command -v node)" dist/report
npx postject dist/report NODE_SEA_BLOB dist/sea-prep.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
The output is a normal executable of 80–110 MB — the Node binary plus your code. It starts the same way Node starts, so the script is your entry point and everything else in the process is ordinary Node.
Why a bundle first
SEA carries one script, and it must be CommonJS. It does not resolve
node_modules from disk the way a normal process does — so if your code
requires a package that is not bundled in, it fails at runtime, on the user's
machine. Everything the entry point loads has to be in that one file. Native addons are the
hard stop: a compiled .node binary cannot be embedded this way.
The trade, in one table
| You gain | You give up |
|---|---|
| One file to copy; no runtime prerequisite | ~100 MB per tool, per platform |
| Deterministic start-up, no module resolution | True cross-compilation: build on each target OS |
| Nothing on disk for a user to tamper with | Hot fixes — a change means a new binary |
A lighter alternative for the same problem: pkg-style bundling, a Docker
image, or simply an installer that checks for Node. SEA is the pick when "one file, no
prerequisites" is the actual requirement — usually a CLI handed to people who are not
developers. Next: the interop problem this whole series has been circling — requiring an ES
module.