PKG Deployment & Usage Guide
Package your Node.js project into a standalone executable for Linux, macOS, and Windows without requiring Node.js installation on the target machine.
Deprecation Notice:
pkgis deprecated as of version 5.8.1 (the final release). Consider Node.js 21+ single executable applications for new projects.
1. Prerequisites
- Node.js: Version 8.x or higher (matching or exceeding your target Node version)
- npm or yarn: For package installation
- Build tools (if using native addons): Python 3.x, C++ compiler (Visual Studio Build Tools on Windows, Xcode Command Line Tools on macOS, or build-essential on Linux)
- For cross-compilation:
- Linux: QEMU user emulation configured (
binfmt_miscsupport) to run foreign architecture binaries - macOS: Rosetta 2 (for building x64 on ARM64, but not reverse)
- Windows: x64 emulation (for building x64 on ARM64, but not reverse)
- Linux: QEMU user emulation configured (
- For macOS signing on Linux:
ldidutility installed - For macOS deployment:
codesignutility (Xcode Command Line Tools)
2. Installation
Install globally for CLI access:
npm install -g pkg@5.8.1
Or install locally per project:
npm install --save-dev pkg@5.8.1
Verify installation:
pkg --version
pkg --help
3. Configuration
Package.json Configuration
Add a pkg field to your package.json to define build parameters:
{
"name": "my-app",
"bin": "index.js",
"pkg": {
"scripts": [
"lib/**/*.js",
"routes/**/*.js"
],
"assets": [
"views/**/*",
"config/*.json",
"node_modules/some-package/assets/**/*"
],
"targets": [
"node18-linux-x64",
"node18-win-x64",
"node18-macos-x64"
],
"outputPath": "dist",
"compress": "GZip"
}
}
Key Configuration Options:
| Field | Description |
|---|---|
scripts | Glob patterns for JS files to include as bytecode |
assets | Non-JS files to embed in the executable (accessible via virtual filesystem) |
targets | Default target platforms when none specified via CLI |
patches | Advanced: inline replacements for problematic packages (see dictionary examples) |
Environment Variables
DEBUG_PKG=1: Enable basic diagnostic output (shows virtual filesystem structure)DEBUG_PKG=2: Enable verbose diagnostics (includes DICT contents)PKG_CACHE_PATH: Override default cache location for downloaded base binaries
4. Build & Run
Basic Usage
Package current directory (follows bin entry in package.json):
pkg .
Package specific entry file:
pkg index.js
Target Specification
Format: nodeRange-platform-arch (e.g., node18-linux-x64, node16-win-arm64)
Build for current platform:
pkg index.js
Build for specific targets:
pkg -t node18-linux-x64,node18-win-x64,node18-macos-x64 index.js
Build for latest Node on specific platforms:
pkg -t linux,macos,win index.js
Use host alias for current platform/Node version:
pkg -t host index.js
Output Control
Specify output filename:
pkg -o myapp-linux index.js
Specify output directory:
pkg --out-path ./dist index.js
Advanced Build Options
Include V8 options (baked into executable):
pkg --options "expose-gc,max-heap-size=34" index.js
Compression (reduces executable size):
pkg --compress GZip index.js
# or
pkg --compress Brotli index.js
Disable bytecode (source files included as plain JS - useful for cross-compilation when emulation unavailable):
pkg --no-bytecode --public-packages "*" --public index.js
Skip native addon builds (if no native dependencies):
pkg --no-native-build index.js
Skip macOS signature (if handling signing manually):
pkg --no-signature index.js
Runtime Behavior
Inside the packaged executable:
- Virtual filesystem root:
/snapshot(Linux/macOS) orC:\snapshot(Windows) - Use
__dirnameandrequire.resolve()to access bundled assets - Native addons (
.nodefiles) are extracted to temporary directories at runtime
Example asset access:
const path = require('path');
const fs = require('fs');
// Access bundled asset
const assetPath = path.join(__dirname, 'config.json');
const config = JSON.parse(fs.readFileSync(assetPath, 'utf8'));
5. Deployment
Distributing Executables
The output binaries are standalone and can be distributed via:
- GitHub Releases: Upload platform-specific binaries as release assets
- Package Managers: Submit to Homebrew (macOS), Chocolatey (Windows), or APT repositories (Linux)
- Direct Download: Host on CDN or company file server
- Docker: Copy binary into minimal images (scratch, alpine, or distroless)
CI/CD Pipeline Example (GitHub Actions)
name: Build Executables
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install pkg
run: npm install -g pkg@5.8.1
- name: Setup QEMU
uses: docker/setup-qemu-action@v2
- name: Build for all platforms
run: |
pkg -t node18-linux-x64,node18-linux-arm64,node18-win-x64,node18-macos-x64 --out-path dist .
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: executables
path: dist/
macOS Code Signing
If pkg fails to ad-hoc sign automatically, or you need a trusted signature:
# Ad-hoc signing (sufficient for local distribution)
codesign --sign - --force dist/myapp-macos
# Developer ID signing (for distribution outside App Store)
codesign --sign "Developer ID Application: Your Name" --force dist/myapp-macos
Handling Native Dependencies
For packages requiring external binaries (like node-notifier or drivelist), ensure:
- Assets are included in
pkg.assetsconfiguration - Paths are patched to use
path.dirname(process.execPath)instead of__dirname(see dictionary examples in repo) - Deploy files are placed alongside the executable in expected relative paths
6. Troubleshooting
"Killed: 9" on macOS
Cause: macOS Gatekeeper/Kernel killing unsigned binaries Solution:
codesign --sign - --force ./executable
# Or disable signing requirement during build:
pkg --no-signature index.js
Bytecode Generation Errors on Cross-Compile
Cause: Cannot run target architecture binary to generate bytecode
Solution: Use --no-bytecode --public-packages "*" --public flags (includes source instead of bytecode)
Missing Files at Runtime
Symptom: Error: Cannot find module or missing assets
Solution:
- Add missing files to
pkg.assetsin package.json - Use
require.resolve()to verify paths resolve within/snapshotvirtual filesystem - Enable debugging:
DEBUG_PKG=1 ./executableto see virtual filesystem contents
Native Module Loading Failures
Symptom: Error: The specified module could not be found
Solution:
- Ensure
.nodefiles are included in assets - Verify the native module is compiled for the target Node version
- Use
--no-native-buildonly if pre-built binaries are included
Large File Size
Optimization:
- Use
--compress GZipor--compress Brotli - Exclude unnecessary files from
scriptsandassetsglobs - Use
--no-dict *to disable built-in package dictionaries if not needed
Debug Mode
Enable verbose logging to inspect the virtual filesystem:
DEBUG_PKG=1 ./myapp # Basic filesystem tree
DEBUG_PKG=2 ./myapp # Verbose including DICT contents
Windows Defender / Antivirus Flags
Cause: Executable packing resembles malware signatures Solution: Sign the executable with a valid code signing certificate (not just ad-hoc)
Architecture-Specific Issues
- macOS ARM64: Experimental; ensure ad-hoc signing is present
- Alpine Linux: Use
alpinetarget (musl libc) instead oflinux(glibc) - Static linking: Use
linuxstatictarget for maximum compatibility across Linux distributions