A while back I put together node-sea, a small boilerplate showing how to package a TypeScript app into a Single Executable Application (SEA) with Node.js — no runtime install required on the target machine, just one binary.
I hadn't touched it in a while, so I went back to check if it still held up. Short answer: the concept is more relevant than ever, but the build process I wrote was already legacy. Node core absorbed most of it. Here's what changed.
Quick recap: what's a Node.js SEA
SEA lets you compile a Node app into a single native binary that runs without Node, npm, or node_modules anywhere in sight. Useful for CLIs you want to hand to someone who doesn't have Node installed, or just for shipping one file instead of a runtime + a tree of dependencies.
The old way
My original build-sea.js did this, by hand, in five steps:
// 1. Generate the blob
execSync('node --experimental-sea-config sea-config.json');
// 2. Copy the node binary itself
const nodePath = execSync('command -v node').toString().trim();
fs.copyFileSync(nodePath, 'dist/hello');
// 3. Strip its existing signature (macOS)
execSync('codesign --remove-signature dist/hello');
// 4. Inject the blob with an external tool
execSync('npx --yes postject dist/hello NODE_SEA_BLOB dist/sea/sea-prep.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \
--macho-segment-name NODE_SEA');
// 5. Re-sign it
execSync('codesign --sign - dist/hello');
That postject step was the annoying one — an external WASM-based tool you had to npx in, with a sentinel fuse string you copy-pasted from the docs and just trusted. It worked, but it wasn't exactly pleasant, and the Node.js team has since said openly that postject had become unmaintained with issues piling up.
What changed: --build-sea
Node 25.5 (January 2026) folded all of that directly into Node core, behind one flag:
node --build-sea sea-config.json
That's it. Blob generation, binary copy, and injection now happen internally — Node vendors the same binary-patching logic postject used to provide (via the LIEF library) instead of shelling out to an npm package. My entire build-sea.js shrank from five manual steps to one command plus the platform-specific codesign call:
execSync('node --build-sea sea-config.json', { stdio: 'inherit' });
if (process.platform === 'darwin') {
execSync('codesign --sign - dist/hello');
}
sea-config.json changed slightly too — output now points straight at the final executable instead of an intermediate .blob file:
{
"main": "./dist/bundled/bundle.js",
"output": "./dist/hello",
"disableExperimentalSEAWarning": true,
"useCodeCache": true
}
That useCodeCache flag is new as well — it pre-compiles the bundle to V8 bytecode at build time, so the binary starts faster. One boolean, free win, no downside for a CommonJS bundle like this one (it does disable dynamic import(), worth knowing if your app uses it).
Before / after
| Old way | --build-sea |
|
|---|---|---|
| Steps | 5 manual steps | 1 command |
| External deps |
postject via npx
|
none |
| Blob handling | you generate + inject it yourself | handled internally |
| Sentinel fuse | copy-pasted magic string | not your problem anymore |
| Codesigning | still manual (macOS/Windows) | still manual (macOS/Windows) |
The catch: it's not on LTS yet
Here's the part that surprised me. I assumed --build-sea had landed on the current LTS by now, so I installed Node 24 "Krypton" (Active LTS as of today) to test — and the flag simply isn't there:
$ node --build-sea sea-config.json
node: bad option: --build-sea
Only --experimental-sea-config, the old blob-generation flag, is available on 24.x. --build-sea currently only exists on Node 26 (the Current release line). The silver lining: Node 26 is an even-numbered release, which means it graduates to Active LTS in October 2026 — a few weeks out at the time of writing. Still, if you're targeting LTS today, you're stuck with the manual flow for a little longer.
It's also worth noting SEA is still officially listed as Experimental (stability 1.1 – "Active development") in the Node.js docs, not Stable, despite what a few blog posts floating around now claim. It's functional and usable, just not API-frozen yet.
Wrapping up
I bumped node-sea to Node 26, rewrote the build script around --build-sea, and refreshed the dependencies (TypeScript, esbuild, commander) while I was at it — kept TypeScript on 5.9 rather than jumping to the new 7.0 Go-native compiler, since ts-node hasn't caught up to its programmatic API yet.
If you're maintaining anything that shells out to postject, this is your sign to simplify it. Repo's here if you want to see the whole thing end to end: github.com/killerwolf/node-sea.
Top comments (0)