Package your Elixir applications as 100% self-contained executables. No Erlang/Elixir installation required on the target machine.
- Self-contained binaries: Single executable with your app + ERTS embedded
- Smart ERTS provisioning: Auto-detects platform or force specific target
- Cross-compilation: Build for Linux (glibc/musl), macOS from any platform
- Zstandard compression: Optimal balance between size and speed
- Multiple execution modes: CLI, TUI, Daemon, and Escript support
- Relativized releases: Portable binaries with no absolute paths
- Automatic build cleanup: Intermediate artifacts are wiped after build, leaving the system pristine while preserving ERTS cache
- Build Environment Isolation: Automatically isolates the build from version managers (
asdf,mise,kerl) to prevent ERTS mismatches - Robust downloads: Automatic retry with exponential backoff on network failures
- Concurrent-safe caching: File-based locking prevents race conditions in multi-process builds
- Clear error messages: Specific error codes for disk full, permission denied, corrupted archives
- Erlang/OTP 25+
- Elixir 1.15+
- Rust (cargo)
- Zstandard (zstd)
When show_banner: true (default), the build process displays a banner image in the terminal. To enable full image support across all terminal emulators, install these dependencies:
# For Sixel support (Alacritty, Ghostty, other terminals)
brew install libsixel
# Optional: for ASCII art fallback
# img2txt is included in libsixel# Ubuntu/Debian
sudo apt install libsixel-tools
# Arch Linux
sudo pacman -S libsixel
# Fedora
sudo dnf install libsixel| Terminal | Protocol | Requires |
|---|---|---|
| iTerm2 | Inline Images | Built-in |
| Ghostty | Kitty protocol | Built-in |
| WezTerm | Kitty protocol | Built-in |
| Alacritty | Kitty protocol | Built-in |
| Kitty | Kitty protocol | Built-in |
| VS Code | Sixel | libsixel |
| foot | Sixel | libsixel |
| Other terminals | ASCII fallback | None |
If no image support is detected, the banner falls back to text-only mode.
# mix.exs
def deps do
[{:batamanta, "~> 1.0", runtime: false}]
enddef project do
[
app: :my_app,
version: "0.1.0",
batamanta: [
erts_target: :auto, # Auto-detect host platform (RECOMMENDED)
execution_mode: :cli, # :cli | :tui | :daemon
compression: 3, # 1-19 (zstd level)
binary_name: "my_app", # Optional: custom binary name
show_banner: true # Optional: show build banner
]
]
end| Option | Type | Default | Description |
|---|---|---|---|
erts_target |
atom | :auto |
Target platform (see below) |
otp_version |
string | :auto |
OTP version (e.g., "28.1") |
execution_mode |
atom | :cli |
:cli, :tui, or :daemon |
compression |
integer | 3 |
Zstd compression level (1-19) |
binary_name |
string | app name | Custom binary name |
show_banner |
boolean | true |
Show build banner |
force_os |
string | nil | Force OS: "linux", "macos", "windows" |
force_arch |
string | nil | Force arch: "x86_64", "aarch64" |
force_libc |
string | nil | Force libc: "gnu", "musl" (Linux only) |
mix batamantaThis generates: my_app-0.1.0-x86_64-linux (or appropriate target)
Batamanta uses a unified ERTS target system for platform specification.
| Target Atom | OS | Arch | Libc | Use Case |
|---|---|---|---|---|
:auto |
- | - | - | Auto-detect host (default) |
:ubuntu_22_04_x86_64 |
Linux | x86_64 | glibc | Debian, Ubuntu, Arch, CachyOS |
:ubuntu_22_04_arm64 |
Linux | aarch64 | glibc | ARM servers, Raspberry Pi 4 |
:alpine_3_19_x86_64 |
Linux | x86_64 | musl | Alpine Linux, containers |
:alpine_3_19_arm64 |
Linux | aarch64 | musl | Alpine on ARM |
:macos_12_x86_64 |
macOS | x86_64 | - | Intel Mac |
:macos_12_arm64 |
macOS | aarch64 | - | Apple Silicon (M1/M2/M3) |
:windows_x86_64 |
Windows | x86_64 | msvc | ✅ Supported |
Force a specific target regardless of host:
batamanta: [
erts_target: :alpine_3_19_x86_64, # Force Alpine musl
execution_mode: :cli
]Or use individual overrides:
batamanta: [
force_os: "linux",
force_arch: "x86_64",
force_libc: "musl"
]# Auto-detect (default)
mix batamanta
# Force specific target
mix batamanta --erts-target alpine_3_19_x86_64
# Force individual components
mix batamanta --force-os linux --force-arch aarch64 --force-libc muslYou specify, you own. If you specify otp_version, that exact version is used. If not specified, a conservative fallback is used.
# Use exact OTP version (recommended for production)
batamanta: [
otp_version: "28.1"
]| Mode | Description | When to Use |
|---|---|---|
| Explicit | Uses exact version specified. Fails if not available in repository. | Production builds, reproducibility |
| Auto | Uses conservative fallback (28.0 → 28.1 → ...). Uses system ERTS if not found. | Development, quick builds |
# Specify exact OTP version
mix batamanta --otp-version 28.1
# Auto mode (default)
mix batamantaIn auto mode, if the exact version is not available:
- Tries
OTP-28.0first (most common) - Then
OTP-28.1,OTP-28.2, etc. - Falls back to system ERTS if nothing found
| Mode | Description | Platform |
|---|---|---|
:cli |
Standard CLI with inherited stdin/stdout/stderr | All |
:tui |
Text UI with raw terminal mode, arrow key navigation | Unix only |
:daemon |
Runs in background, no terminal I/O | Unix only |
| Format | Description | Notes |
|---|---|---|
:release |
Full OTP release with ERTS (default) | Larger (~60-70MB), self-contained |
:escript |
Lightweight escript bundle with minified ERTS | Smaller (~20MB), self-contained |
| OS | Architectures | Modes | Status |
|---|---|---|---|
| macOS 11+ | x86_64, aarch64 | CLI, TUI, Daemon | ✅ Full Support |
| Linux (glibc) | x86_64, aarch64 | CLI, TUI, Daemon | ✅ Full Support |
| Linux (musl) | x86_64, aarch64 | CLI, Daemon | ✅ Supported |
| Windows 10+ | x86_64 | CLI | ✅ Supported |
| OTP | Elixir | Status |
|---|---|---|
| 25 | 1.15 | ✅ Minimum Supported |
| 26 | 1.15, 1.16 | ✅ Supported |
| 27 | 1.15, 1.16, 1.17 | ✅ Supported |
| 28 | 1.16, 1.17, 1.18+ | ✅ Latest |
- ❌ Windows + TUI mode (requires Unix terminal)
- ❌ Windows + Daemon mode (requires Unix process management)
- ❌ OTP < 25 (missing required BEAM features)
- ❌ Elixir < 1.15 (missing required language features)
If you see a warning like:
⚠️ libc mismatch detected!
Expected: glibc (Debian/Ubuntu/Arch/Fedora)
Detected: musl libc (Alpine)
This means your system's libc type doesn't match the expected ERTS target.
Solution 1: Let Batamanta auto-detect (recommended)
batamanta: [
erts_target: :auto # Auto-detects musl vs glibc
]Solution 2: Force specific target
batamanta: [
erts_target: :alpine_3_19_x86_64 # Force musl
]Solution 3: Use CLI override
mix batamanta --erts-target alpine_3_19_x86_64If ERTS download fails with 404 error on musl systems, try one of these solutions:
Solution 1: Use auto-detection (recommended)
batamanta: [
erts_target: :auto # Auto-detects musl vs glibc
]Solution 2: Use a specific OTP version
batamanta: [
otp_version: "28.0" # Try an older version that may have musl builds
]Solution 3: Build custom ERTS for musl (advanced)
# On Alpine Linux
apk add erlang-dev
cd /tmp
git clone https://github.com/erlang/otp.git
cd otp
./otp_build autoconf
./configure --prefix=/usr/local
make
make install
tar -czf musl-erts.tar.gz /usr/local/lib/erlangIf the binary works on build machine but fails on target:
Check libc compatibility:
# On build machine
ldd --version
# On target machine
ldd --version
# They should match (both glibc or both musl)Solution: Build for oldest supported glibc version
# Use Ubuntu 22.04 target (most compatible glibc)
batamanta: [
erts_target: :ubuntu_22_04_x86_64
]Install Rust targets:
rustup target add x86_64-unknown-linux-gnu
rustup target add aarch64-unknown-linux-gnuBuild with explicit target:
mix batamanta --erts-target ubuntu_22_04_x86_64Batamanta uses multiple methods in order:
ldd --version- Most reliable, checks output for "musl" or "glibc"- Dynamic loader files - Checks
/lib/ld-musl-*.sovs/lib64/ld-linux-*.so /etc/os-release- ChecksID=alpine,ID=void, etc./proc/self/maps- Advanced, checks loaded libraries
Detection always falls back to glibc if uncertain (90%+ of systems use glibc).
Batamanta attempts to download pre-compiled ERTS from Hex.pm builds. If the download fails:
⚠️ Could not download ERTS, using system ERTS instead.
The build continues using the system ERTS (similar to Bakeware). This means:
- ✅ Build succeeds - Your application compiles
⚠️ Binary requires ERTS - Target machine needs compatible Erlang/Elixir- ✅ Portable within same OS - Works on machines with same libc type
For production self-contained binaries:
- Ensure network access during build
- Use specific ERTS version:
batamanta: [otp_version: "26.2.5"] - Ensure the target platform has pre-built ERTS available
Override configuration via command line:
# Use auto-detection (default)
mix batamanta
# Force ERTS target
mix batamanta --erts-target alpine_3_19_x86_64
# Force individual components
mix batamanta --force-os linux --force-arch aarch64 --force-libc musl
# Adjust compression level
mix batamanta --compression 9
# Combine options
mix batamanta --erts-target ubuntu_22_04_arm64 --compression 5| Flag | Description |
|---|---|
--erts-target |
Override ERTS target atom |
--otp-version |
Specify exact OTP version (e.g., "28.1") |
--force-os |
Force OS: linux, macos, windows |
--force-arch |
Force architecture: x86_64, aarch64 |
--force-libc |
Force libc: gnu, musl (Linux only) |
--compression |
Zstd compression level (1-19) |
Use Erlang's :init to read arguments:
defmodule MyApp do
use Application
@impl true
def start(_type, _args) do
args =
:init.get_plain_arguments()
|> Enum.map(&to_string/1)
|> Enum.reject(&(&1 == "--"))
case args do
["hello", name] -> IO.puts("Hello, #{name}!")
_ -> IO.puts("Usage: my_app hello <name>")
end
System.halt(0)
end
endDon't forget System.halt/1 when your CLI finishes!
-
Auto-detection: Batamanta detects your host platform using:
:os.type()for OS identification:erlang.system_info(:system_architecture)for architectureldd --versionfor libc detection on Linux (glibc vs musl)
-
Download: Fetches pre-compiled ERTS from Hex.pm builds or from the Batamanta ERTS Repository
-
Cache: Stores in
~/.cache/batamanta/for reuse -
Package: Bundles your release + ERTS into a single compressed tarball
-
Compile: Rust dispenser embeds the payload and handles extraction at runtime
Batamanta version 1.4.0+ includes Batamanta.EnvCleaner, which automatically handles environment isolation during binary generation.
When using version managers like asdf, mise, or kerl, your shell's PATH points to shimmed versions of Erlang and Elixir. If these versions differ from the ERTS being embedded, you may encounter:
- "Corrupt atom table" crashes
- Inconsistent behavior between build-time and runtime
- Compilation failures in CI environments
When you run mix batamanta, the tool:
- Detects and filters out version manager paths from the
PATH. - Sanitizes the environment to include only essential system variables.
- (In Escript mode) Prepends the downloaded ERTS bin directory to the
PATHduring compilation, ensuring 100% version parity.
This mechanism ensures that the binary you build is exactly matched to the runtime environment it will use.
Batamanta uses a separate repository for pre-compiled ERTS binaries:
This repository hosts pre-compiled Erlang Run-Time System (ERTS) binaries for:
- macOS: aarch64 (Apple Silicon)
- Linux (glibc): x86_64 & aarch64
- Linux (musl): x86_64 & aarch64
The binaries are compiled from official Erlang/OTP sources and are subject to the Apache License 2.0 (see the repository for details).
Batamanta auto-detects using ldd --version. If this fails:
# Check what ldd reports
ldd --version
# Force specific target
mix batamanta --erts-target ubuntu_22_04_x86_64Ensure you're building with the correct deployment target:
batamanta: [
erts_target: :macos_12_x86_64 # or :macos_12_arm64
]Install Rust targets:
rustup target add x86_64-unknown-linux-gnu
rustup target add aarch64-unknown-linux-gnuThen build:
mix batamanta --erts-target ubuntu_22_04_x86_64Ensure musl development headers are installed:
# Alpine
apk add musl-dev
# Or use the Alpine Docker image
docker run --rm -v $(pwd):/app -w /app elixir:1.18-alpine ...- Detect: Auto-detect or resolve manual target configuration
- Fetch: Download ERTS from Hex.pm builds
- Release: Compile your Elixir code with
mix release - Package: Bundle release + ERTS with Zstd compression
- Compile: Build Rust dispenser that embeds the payload
- Run: Dispenser extracts payload and spawns Erlang VM
Batamanta can package projects that use mix escript.build as self-contained binaries:
def project do
[
app: :my_escript_app,
version: "0.1.0",
batamanta: [
format: :escript, # :escript or :release
escript_module: MyEscriptApp.CLI # Module with main/1 function
],
escript: [
main_module: MyEscriptApp.CLI
]
]
endThe project should have a module with a main/1 function:
defmodule MyEscriptApp.CLI do
def main(args) do
IO.puts("Escript running with args: #{inspect(args)}")
end
endNote: The format: :escript in batamanta: is optional if your project already has escript: configuration in mix.exs - Batamanta auto-detects escript format. But you can include it explicitly for clarity.
Run the test matrix locally:
# Test across Linux distributions (requires Docker)
./docker_matrix.sh
# Run smoke tests manually
cd smoke_tests/test_cli && mix batamanta && ./test_cli-* arg1 arg2
cd smoke_tests/test_tui && mix batamanta && ./test_tui-*
cd smoke_tests/test_daemon && mix batamanta && ./test_daemon-* &
cd smoke_tests/test_escript && mix batamanta && ./test_escript --helpMIT
