Skip to main content
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
package
derivation
required
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:
  1. package.json "main" field
  2. build/index.js
  3. Fails if none found
allow-read
boolean
default:true
Allow file system read access.
allow-write
boolean
default:true
Allow file system write access.
allow-net
boolean
default:true
Allow network access.
allow-env
boolean
default:true
Allow environment variable access.
allow-run
boolean
default:true
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";
};

Multi-Platform Builds

flake.nix
{
  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:
TargetOSArchitecture
x86_64-unknown-linux-gnuLinuxx86_64
aarch64-unknown-linux-gnuLinuxARM64
x86_64-apple-darwinmacOSIntel
aarch64-apple-darwinmacOSApple Silicon
x86_64-pc-windows-msvcWindowsx86_64
All targets use Deno v2.6.5 runtime.

Implementation Details

Entrypoint Detection

The entrypoint is determined by this priority:
  1. Explicit entrypoint parameter
  2. package.json "main" field (via jq)
  3. build/index.js (common build output)
  4. 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

CLI Tool Distribution

{
  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

  1. Specify permissions explicitly: Don’t rely on defaults for production
  2. Test binaries on target platforms: Cross-compiled binaries should be tested
  3. Use explicit entrypoints: Reduces build-time guesswork
  4. 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

Build docs developers (and LLMs) love