Pack a Gleam application into a single, self-contained executable. No Erlang installation required on the target machine.
gleepack build takes your Gleam project and produces a standalone binary that
you can ship directly. The binary contains:
- A full, patched version of the Erlang VM (BEAM)
- Your compiled application code and files
- All OTP libraries and other dependencies
Running the binary starts the VM and launches your application. There is nothing to install on the target machine.
The main use case is for building simple-ish CLIs, TUIs, and dev tools targeting the Erlang backend, without requiring the user to install Erlang too.
It is not suitable for distributing servers and other long-running software. Please use a full OTP release or a gleam erlang-shipment for this.
Download the right binary from one of the gleepack releases on the
releases page.
Altneratively, you can install gleepack as a Gleam dev dependency in your app:
[dev_dependencies]
gleepack = { git = "git@github.com:yoshi-monster/gleepack.git", ref = "main" }From inside a Gleam project:
gleepack build
# or when using the gleam dependency:
gleam run -m gleepack buildThis produces ./build/<project-name> by default. On the first run, gleepack
downloads the runtime and OTP toolchain for your current platform automatically.
Running gleepack without arguments outputs useful information about all available commands.
| Flag | Description |
|---|---|
--output <path> |
Write the executable to <path> instead of the default. |
--module <name> |
Call <name>.main() as the entry point (default: your project name). |
--target <slug> |
Build for a specific target. Repeat to build for multiple targets. |
Flags can also be set permanently in your gleam.toml:
[tools.gleepack]
output = "dist/myapp"
module = "myapp/cli"
targets = ["aarch64-macos-otp-29.0", "amd64-linux-otp-29.0"]A target identifies a CPU architecture, operating system, and OTP version. The default target matches your current platform.
| Target slug | Platform |
|---|---|
aarch64-macos-otp-29.0 |
Apple Silicon macOS |
amd64-linux-otp-29.0 |
x86-64 Linux |
aarch64-linux-otp-29.0 |
ARM64 Linux |
amd64-windows-otp-29.0 |
x86-64 Windows |
Use the gleepack targets subcommands to list, add, or remove targets.
When developing, you can download and use the gleepack binary from the
releases page
to replace the need to install Erlang on your devlopment machine.
Instead of using gleam run, you can run your project using gleepack.
# run the project using gleepack
gleepack run
gleepack test
gleepack dev
# run a module using gleepack
gleepack run --module lustre/dev startgleepack re-implements the Gleam build tool, invoking the Gleam compiler,
BEAM compiler, and Rebar3/Mix to build BEAM bytecode suitable for your
selected targets and all its dependencies. All .beam files are stripped
to minimise bundle size.
The compiled .beam files, private directories, and OTP library modules
are zipped into an Erlang release archive.
The archive is appended to a patched BEAM runtime to produce a self-contained executable. This makes a polyglot file that's at the same time an executable and a ZIP file - a magic trick that works because ZIP files work with a file footer instead of a header.
The VM is patched to intercept file I/O at startup:
- On launch, the entry point detects the appended ZIP and unpacks it in memory.
- BEAM File I/O calls are intercepted andredirected to that in-memory file system if they point to a file inside the archive. All other paths pass through to the OS unchanged.
This means the final binary carries everything it needs and requires no external Erlang or OTP installation.
The patched BEAM differs from a standard Erlang installation in a few additional ways that are worth knowing about.
Files inside your application's priv directory are stored in the in-memory
archive. They can be read normally via code:priv_dir/1 and standard file
APIs, but any attempt to write to those paths will fail. Write to a
user-writable location such as a temp directory or the user's home directory
instead.
Native Implemented Functions (NIFs) are shared libraries loaded at runtime from disk. There are some tricks that would allow me to load them, but they would require deeper changes to how NIFs are built - more specifically, I would have to ship a C cross-compiler with a custom linker. This is a huge task that I might tackle at a later time.
In general, the recommendation in Gleam is to avoid NIFs as they can break the guarantees of the VM. We have so far identified 2 major use cases for NIFs. I would like to first explore which are actually needed in practice, and whether a custom build that includes them would also solve the problem.
Built-in NIFs (like crypto or asn1) are bundled within the executable
and work directly as long as the application is listed as a dependency.
OTP normally resolves hostnames via a helper port program (inet_gethost).
The patched BEAM removes this helper and replaces it with direct
gethostbyname calls.
Port programs and spawned OS processes are managed through erl_child_spawn
on Unix systems, another OTP helper binary. Gleepack replaces this program
with direct posix_spawn calls.
One small behavioural difference falls out of this: stock OTP calls
setsid() in spawned children, putting each one in its own session detached
from the controlling terminal, gleepack does not. Children stay in the
parent's process group, so terminal signals (Ctrl-C, SIGHUP on disconnect)
reach them too. This means a long-running subprocess dies with the parent
instead of being orphaned - the behaviour you'd want from a CLI tool, but a
divergence from what an Erlang application linked against stock OTP would
see.
gleepack is written in Gleam with a small amount of C for the BEAM patches.
- Gleam
- Erlang/OTP
- A C compiler (ideally clang)
- macOS or Linux
# Builds every locally into ./build/
make
# Build individual components
make runtime toolchain cli
# Copy things into locations I use to test locally
make copy-devThe workflow right now is a bit rough since it's only been me working on it and i didn't super mind the friction until things were ready. But it can definitely be improved a lot! Please reach out to me on Discord or make an issue if you have questions!