PAX

PAX is a Perl-native adaptive compiler and standalone binary packager.

Introduction

PAX exists to turn a Perl application plus its repeatable build inputs into one standalone executable.

Without that layer, a normal Perl deployment usually depends on some mix of:

PAX changes that deployment shape. The target artifact is one executable that can carry compiled code units, packaged runtime payloads, embedded assets, and native artifacts where PAX can prove a region is safe to specialize.

In bundled-runtime mode that includes the packaged helper programs and the linked shared libraries and SONAME aliases required by bundled XS modules, so helper commands and query/runtime helpers still work after the original source tree and CPAN installation are gone.

The design goal is not "replace Perl with magic". The design goal is:

The public command surface is intentionally small:

perl bin/pax help
perl bin/pax build
perl bin/pax run

Everything else in the repository is compiler/runtime implementation, test coverage, or release tooling. Users should not call internal diagnostic subcommands through bin/pax.

What You Get

Goals

Main Concepts

Quick Start

Build from a local paxfile.yml:

perl bin/pax build

Long builds print a DD-style task rundown on stderr by default. On a real terminal the board redraws live; in non-interactive runs it prints a static rundown. The build path is broken into concrete checkpoints such as code-unit compilation, application metadata inference, dependency analysis, native artifact analysis, manifest writing, and launcher compilation. The code-unit phase is further split into source discovery, entrypoint compilation, application unit compilation, and dependency unit compilation so long builds keep moving visibly. Application unit compilation includes the current file name, so a slow module no longer looks like a frozen counter. To suppress it:

PAX_PROGRESS=0 perl bin/pax build --compact

Build an explicit entrypoint:

perl bin/pax build bin/my-app

Build to a specific output path:

perl bin/pax build -o ./build/my-app bin/my-app
perl bin/pax build --output ./build/my-app bin/my-app

Build and immediately run:

perl bin/pax run -- version
perl bin/pax run bin/my-app -- version

pax run uses the same build inputs as pax build, writes or refreshes the standalone executable, and then executes that binary with arguments after --.

Interpreter-style execution is also available when the pax executable is used as a shebang target for a Perl script:

#!/usr/local/bin/pax
use strict;
use warnings;
print "hello from pax shebang\n";

In that mode, pax treats the script path as a direct execution target instead of requiring an explicit build or run command.

CLI Contract

usage:
  pax build ...
  pax run ...

Public commands:

Interpreter mode:

Common options:

paxfile.yml

With no positional entrypoint, pax build and pax run read paxfile.yml. CLI flags override file values. When a positional entrypoint is supplied on the CLI, PAX treats that target as an isolated build and does not silently inherit libs, source_roots, assets, asset_dirs, cpanfiles, or app metadata from the ambient default paxfile.yml. An explicit --paxfile still applies its manifest defaults.

Example:

name: example-app
entrypoint: bin/example-app
output: build/example-app
libs:
  - lib
source_roots:
  - lib
assets:
  - share/banner.txt
asset_dirs:
  - share/public
cpanfiles:
  - cpanfile
runtime_mode: bundled_perl

Output path precedence:

  1. CLI --output / -o
  2. paxfile.yml output
  3. fallback .pax/standalone/<name>/<name>

Manual

Installation

For local development, install the distribution prerequisites and run from the repository checkout:

cpanm --installdeps .
perl bin/pax help

For release packaging, Dist::Zilla must also be available:

cpanm Dist::Zilla

First Build

The simplest workflow is a project-local paxfile.yml:

name: example-app
entrypoint: bin/example-app
output: build/example-app
libs:
  - lib
cpanfiles:
  - cpanfile
runtime_mode: bundled_perl

Then build:

perl bin/pax build

Run the result:

./build/example-app

Build Without paxfile.yml

PAX does not require a manifest when the CLI provides the required build shape:

perl bin/pax build -o ./build/example-app bin/example-app

That keeps one-off builds and self-hosting neutral even inside repositories that ship their own paxfile.yml. Extra roots, assets, and CPAN policy files must be declared explicitly on the CLI in that mode.

Inline entrypoints use the same public surface. -I adds Perl library roots, -M loads and imports modules before execution, and -e supplies the entrypoint code directly:

perl bin/pax build \
  -I lib \
  -MDateTime \
  -e 'print DateTime->now'

pax run accepts the same switches:

perl bin/pax run \
  -I lib \
  -MDateTime \
  -e 'print DateTime->now'

Self Compile

PAX can build PAX itself:

perl bin/pax build -o /tmp/pax bin/pax
/tmp/pax help

When the original source paths still exist, that self-built standalone pax binary can also rebuild from another standalone pax binary input.

That same self-built binary can then build another standalone application from its own paxfile.yml. It can also rebuild from another standalone pax binary when the original source checkout is no longer present, because the build path carries an embedded source snapshot for the application units it needs to rebuild.

For plain executable Perl scripts, the standalone build path now keeps source analysis static. pax build does not need to execute a long-running script just to inspect it, and recognized numeric loop subs can be rebound through packaged native artifacts before the script's top-level work starts.

Asset Embedding

Assets are copied into the executable payload and extracted into a private runtime directory when the binary starts. Framework code can read them through the embedded asset root prepared by the PAX runtime.

Example:

perl bin/pax build \
  --name webapp \
  --lib lib \
  --source-root lib \
  --asset-dir share \
  --cpanfile cpanfile \
  --runtime-mode bundled_perl \
  --output ./build/webapp \
  bin/webapp

This pattern supports web applications that include Perl modules, templates, CSS, JavaScript, and other static files.

Web Applications

PAX supports the single-binary packaging shape for framework applications that combine:

The validated SOW-03 proof includes a Dancer2 + Plack/Starman + Template Toolkit web application packaged as one executable and deployed through a multi-stage Docker flow.

Docker Deployment

Two-stage pattern for a generic project:

FROM perl:5.42 AS builder
WORKDIR /workspace
COPY . /workspace
RUN cpanm --installdeps .
RUN perl bin/pax build --output /out/app

FROM debian:bookworm-slim
COPY --from=builder /out/app /usr/local/bin/app
CMD ["/usr/local/bin/app"]

The final stage receives only the built executable. It does not need the source tree, asset tree, cpanfile, or web framework installation when the binary was built in bundled runtime mode.

For an external application, the validated deployment pattern is:

  1. build a standalone pax binary
  2. copy that pax binary into the application build stage
  3. compile the application into its own standalone binary
  4. copy only that final application binary into the runtime stage

Architecture

PAX packages an application through these stages:

  1. Entrypoint and manifest loading.
  2. Dependency and source-root discovery.
  3. Code unit compilation into PCU or hybrid PCU records where supported.
  4. Native artifact packaging for supported hot regions.
  5. Asset and runtime payload embedding.
  6. Standalone launcher generation.
  7. Runtime extraction and dispatch with fallback safety.

Compilation is adaptive. If a module shape fails, the preferred fix is a reusable compiler, loader, dependency discovery, or runtime improvement that works for other projects with the same structure.

Why The Two-Command Surface Works

PAX used to expose more internal diagnostic and build commands at the CLI surface. SOW-03 intentionally collapsed that down to:

That keeps the operator workflow small while still allowing the internal Perl modules to carry richer build, inspection, and validation logic behind the public facade.

Known Limits

FAQ

Is PAX only for one specific project?

No. PAX uses DD and other applications as validation corpora, but core compiler and runtime logic are expected to stay neutral and reusable.

Does PAX guarantee Rust-like speed for all Perl code?

No. The target is to package the whole application correctly and accelerate hot paths that PAX can prove are safe to specialize. Dynamic regions still use fallback execution.

Is PAX still case by case?

Not by project name or package name. It should stay neutral across arbitrary Perl applications. But today it is still case by supported code shape. If PAX recognizes a loop or leaf routine class and can lower it safely, it can do very well. The five examples above are proven cases on this host: invoice rollup, retry budget planning, shard weight planning, backfill window checksum, and cohort retention counting. In those runs stock Perl took about 116s to 120s, while the standalone binaries finished in about 1.16s to 1.27s. If the workload stays in dynamic Perl semantics, it will package correctly but may run close to stock Perl speed.

What should I report as a performance issue?

Report any case where:

For a useful report, include:

Does pax run require a separate app server?

No. Under SOW-03, pax run builds the standalone executable and then runs that binary directly.

Can PAX build web applications with embedded static assets?

Yes. The validated packaging path includes templates, CSS, JavaScript, and framework code embedded into one standalone executable.

Repository Map