· tutorials · 3 min read
By AnkitPackaging Node.js Application with Code Security
Secure your Node.js app by compiling JavaScript to V8 bytecode using Bytenode. This guide covers bundling, PM2 setup, Docker build, and protecting source code for on-premise deployments.

Objective
The objective here is to package your Node.js application using Bytenode to enhance source code protection by compiling JavaScript into V8 bytecode. This technique makes sure reverse engineering significantly harder than traditional bundling or obfuscation approaches. Well it’s not fully secure, but way better than Node.js’s SEA.
You’ll learn how to:
- Bundle and compile .js files into secure .jsc bytecode using Bytenode.
- Launch the compiled app via Node.js or PM2 reliably.
- Dockerize the system without exposing raw source code.
Why Not pkg or SEA?
pkg: Deprecated and lacks support for modern features like embedding V8 bytecode.
SEA (Single Executable Applications): Designed for packaging, not security. It includes raw, readable source code within the binary.
Bytenode: Converts .js
files into .jsc
bytecode files using V8’s internal compilation. Requires Node.js but offers meaningful IP protection.
Step 1: Prepare Your Node.js Project
Before starting:
Ensure your app is modular (can split logic into separate files). Make sure your entry file is something like index.js.
Structure:
/your-app
├── package.json
├── src/
│ ├── index.js
│ └── utils.js
Step 2: Install Bytenode
Install Bytenode globally or locally:
npm install -g bytenode
# or for local
npm install --save-dev bytenode
Step 3: Compile JavaScript to Bytecode
Run Bytenode to compile:
bytenode --compile src/index.js
This creates:
src/index.jsc
Note: Your original .js
file can now be excluded or deleted from the distributed build. Ensure to do this, else your build shall not be secure.
Step 4: Create a Launcher Script
Bytenode cannot run .jsc
directly with node. You need a loader script. Save the following in launcher.js
.
require('bytenode');
require('./src/index.jsc');
Step 5: Bundle for Production (Optional)
To increase security, combine esbuild or ncc with Bytenode:
esbuild src/index.js --bundle --platform=node --outfile=bundle.js
bytenode --compile bundle.js
Then update launcher.js accordingly:
require('bytenode');
require('./bundle.jsc');
Step 6: Run
For Development
To test locally:
node launcher.js
Dockerize the Build
You can build a Docker container with compiled bytecode.
Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install -g bytenode && npm ci
CMD ["node", "launcher.js"]
Ensure you don’t copy .js
source into the image, only .jsc
.
PM2 (Process Manager)
Because PM2 does not support .jsc
files directly, define a launcher in ecosystem.config.js
:
module.exports = {
apps: [
{
name: "your-app-name",
script: "./launcher.js",
instances: 1,
exec_mode: "fork"
}
]
};
Besides, you can tell PM2 to use bytenode
directly using custom interpreter, as shown below:
module.exports = {
apps: [
{
"name": "my-app",
"script": "./index.jsc",
"instances": 1,
"watch": false,
"exec_interpreter": "bytenode",
"exec_mode": "fork"
}
]
}
Step 7: Sanity Checks
Use tools like strings or cat to inspect .jsc
files. Ensure sensitive data isn’t visible. Clean your build directory before compiling.
Summary
So finally, if you’re looking to protect your Node.js source code—especially in scenarios like on-premise deployments or client-shipped builds, Bytenode is one of the more practical options available today. It compiles your .js
files into V8 bytecode (.jsc
), which is still runnable but much harder to read or reverse engineer compared to plain JavaScript.
We initially considered options like pkg or Node’s SEA (Single Executable Applications), but neither provided any real code protection. SEA actually just zips up your source and unpacks it at runtime, making it easy to extract. The source code with variable names, function names are visible on naked eye, amazingly easy and reuse the code. By contrast, Bytenode gives you something that’s not directly human-readable and isn’t as trivial to modify.
Of course, nothing is 100% secure—determined attackers can always go low-level. There is always a tradeoff of efforts vs security level. Bytenode is relatively simple to integrate into a build or Docker workflow using a launcher script, or PM2 workarounds.
In short, if you just want to avoid handing over readable source code, and you’re okay with keeping Node.js as a runtime dependency, this setup works well without being overly complex.