deno.compile transforms Deno projects into standalone executables for multiple platforms using deno compile.
Overview
Take a TypeScript/JavaScript project and compile it to a native binary:
packages.myapp = lib.deno.compile {
package = pkgs.buildNpmPackage {
pname = "myapp";
version = "1.0.0";
src = ./.;
npmDepsHash = "sha256-...";
};
target = "x86_64-unknown-linux-gnu";
entrypoint = "src/main.ts";
};
Function Signature
deno.compile :: {
package,
target?,
entrypoint?,
allow-read?,
allow-write?,
allow-net?,
allow-env?,
allow-run?,
...
} -> Derivation
A package built with buildNpmPackage, stdenv.mkDerivation, or similar. Should contain your TypeScript/JavaScript source.
target
string
default:"\"x86_64-unknown-linux-gnu\""
Target platform triple. Supported targets:
"x86_64-unknown-linux-gnu"
"aarch64-unknown-linux-gnu"
"x86_64-apple-darwin"
"aarch64-apple-darwin"
"x86_64-pc-windows-msvc"
entrypoint
string
default:"auto-detected"
Path to the entry file. If not specified, tries:
package.json "main" field
build/index.js
- Fails if none found
Allow file system read access.
Allow file system write access.
Allow environment variable access.
Allow running subprocesses.
Return Value
Returns a modified derivation with:
- Compiled binary in
$out/bin/<name>
.exe extension for Windows targets
meta.mainProgram set correctly
doCheck = false (tests disabled)
Examples
Basic Compilation
let
myapp = pkgs.buildNpmPackage {
pname = "myapp";
version = "1.0.0";
src = ./.;
npmDepsHash = "sha256-...";
};
in
{
packages.default = lib.deno.compile {
package = myapp;
};
}
Cross-Compile to Windows
packages.myapp-windows = lib.deno.compile {
package = pkgs.buildNpmPackage { /* ... */ };
target = "x86_64-pc-windows-msvc";
entrypoint = "src/main.ts";
};
{
outputs = { self, nixpkgs, nur }:
nur.lib.mkFlake { } (system:
let
pkgs = nixpkgs.legacyPackages.${system};
lib = nur.lib.${system};
myapp = pkgs.buildNpmPackage {
pname = "myapp";
version = "1.0.0";
src = ./.;
npmDepsHash = "sha256-...";
};
in
{
packages = {
default = myapp;
# Linux
linux-x64 = lib.deno.compile {
package = myapp;
target = "x86_64-unknown-linux-gnu";
};
linux-arm64 = lib.deno.compile {
package = myapp;
target = "aarch64-unknown-linux-gnu";
};
# macOS
macos-x64 = lib.deno.compile {
package = myapp;
target = "x86_64-apple-darwin";
};
macos-arm64 = lib.deno.compile {
package = myapp;
target = "aarch64-apple-darwin";
};
# Windows
windows = lib.deno.compile {
package = myapp;
target = "x86_64-pc-windows-msvc";
};
};
}
);
}
Restricted Permissions
For security-sensitive apps, restrict permissions:
packages.secure-app = lib.deno.compile {
package = myapp;
# Deny everything by default
allow-read = false;
allow-write = false;
allow-net = false;
allow-env = false;
allow-run = false;
};
Custom Entrypoint
packages.cli = lib.deno.compile {
package = pkgs.buildNpmPackage { /* ... */ };
entrypoint = "cli/index.ts";
allow-net = false; # CLI doesn't need network
};
With Override
Since compile uses lib.makeOverridable:
let
baseCompile = lib.deno.compile {
package = myapp;
};
in
{
# Override target
packages.macos = baseCompile.override {
target = "aarch64-apple-darwin";
};
}
Supported Targets
Deno runtime binaries (denort) are bundled for these platforms:
| Target | OS | Architecture |
|---|
x86_64-unknown-linux-gnu | Linux | x86_64 |
aarch64-unknown-linux-gnu | Linux | ARM64 |
x86_64-apple-darwin | macOS | Intel |
aarch64-apple-darwin | macOS | Apple Silicon |
x86_64-pc-windows-msvc | Windows | x86_64 |
All targets use Deno v2.6.5 runtime.
Implementation Details
Entrypoint Detection
The entrypoint is determined by this priority:
- Explicit
entrypoint parameter
package.json "main" field (via jq)
build/index.js (common build output)
- Error if none found
if [[ -n "${entrypoint}" ]]; then
ENTRYPOINT="${entrypoint}"
elif jq -e 'has("main")' package.json; then
ENTRYPOINT=$(jq -r '.main' package.json)
elif [[ -f build/index.js ]]; then
ENTRYPOINT="build/index.js"
else
echo "Could not determine entrypoint. Please specify it explicitly."
exit 1
fi
denort Installation
The denort (Deno runtime) binary is pre-fetched for each target and installed into Deno’s cache:
mkdir -p "$DENO_DIR/dl/release/v${deno.version}"
cp ${denort."${target}"} "$DENO_DIR/dl/release/v${deno.version}/denort-${target}.zip"
This avoids network access during build.
Compilation Command
The actual compilation:
deno compile \
--no-check \
${if allow-read then "--allow-read" else "--deny-read"} \
${if allow-write then "--allow-write" else "--deny-write"} \
${if allow-net then "--allow-net" else "--deny-net"} \
${if allow-env then "--allow-env" else "--deny-env"} \
${if allow-run then "--allow-run" else "--deny-run"} \
--target ${target} \
--output "$out/bin/${bin}" "$ENTRYPOINT"
Source Location
Implemented in: libs/deno/compile.nix:8
Common Patterns
{
packages = {
# Native build for development
default = myCliTool;
# Release builds for distribution
cli-linux = lib.deno.compile {
package = myCliTool;
target = "x86_64-unknown-linux-gnu";
};
cli-macos = lib.deno.compile {
package = myCliTool;
target = "aarch64-apple-darwin";
};
cli-windows = lib.deno.compile {
package = myCliTool;
target = "x86_64-pc-windows-msvc";
};
};
apps.release = lib.mkApps {
script = ''
nix build .#cli-linux
nix build .#cli-macos
nix build .#cli-windows
tar czf dist/cli-linux.tar.gz -C result/bin .
tar czf dist/cli-macos.tar.gz -C result/bin .
zip dist/cli-windows.zip -j result/bin/*.exe
'';
deps = [ pkgs.nix pkgs.gnutar pkgs.gzip pkgs.zip ];
};
}
TypeScript Server
packages.server = lib.deno.compile {
package = pkgs.buildNpmPackage {
pname = "my-server";
src = ./.;
npmDepsHash = "sha256-...";
};
entrypoint = "src/server.ts";
allow-net = true;
allow-env = true;
allow-read = true;
allow-write = false; # Server doesn't write files
allow-run = false; # Server doesn't spawn processes
};
Best Practices
- Specify permissions explicitly: Don’t rely on defaults for production
- Test binaries on target platforms: Cross-compiled binaries should be tested
- Use explicit entrypoints: Reduces build-time guesswork
- Keep binaries small: Deno includes the full runtime, but tree-shaking helps
Go Utilities
Cross-compile Go applications
Rust Utilities
Cross-compile Rust with cargo-zigbuild