Package your Elixir applications as 100% self-contained executables. No Erlang/Elixir installation required on the target machine.
Features
- 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
Requirements
- Erlang/OTP 25+
- Elixir 1.15+
- Rust (cargo)
- Zstandard (zstd)
Banner Dependencies (Optional)
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:
macOS
# For Sixel support (Alacritty, Ghostty, other terminals)
brew install libsixel
# Optional: for ASCII art fallback
# img2txt is included in libsixelLinux
# Ubuntu/Debian
sudo apt install libsixel-tools
# Arch Linux
sudo pacman -S libsixel
# Fedora
sudo dnf install libsixelTerminal Compatibility
| 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.
Quick Start
1. Add Dependency
# mix.exs
def deps do
[{:batamanta, "~> 1.0", runtime: false}]
end2. Configure (Auto-detect)
def 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
]
]
endConfiguration Options
| 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) |
3. Build
mix batamanta
This generates: my_app-0.1.0-x86_64-linux (or appropriate target)
ERTS Target System
Batamanta uses a unified ERTS target system for platform specification.
Supported Targets
| 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 |
Manual Override
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"
]CLI Override
# 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 muslOTP Version Control
You specify, you own. If you specify otp_version, that exact version is used. If not specified, a conservative fallback is used.
Configuration
# Use exact OTP version (recommended for production)
batamanta: [
otp_version: "28.1"
]Behavior
| 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 |
CLI Override
# Specify exact OTP version
mix batamanta --otp-version 28.1
# Auto mode (default)
mix batamantaVersion Resolution
In 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
Execution Modes
| 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 |
Output Formats
| 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 |
Compatibility Matrix
Operating Systems
| 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 Versions
| 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 |
Restrictions
- ❌ 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)
Troubleshooting: Linux musl/glibc
Problem: "libc mismatch detected" Warning
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_64Problem: ERTS download fails on Alpine/musl
If 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/erlangProblem: Binary doesn't run on target system
If 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
]Problem: Cross-compilation from macOS to Linux
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_64How libc Detection Works
Batamanta 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).
ERTS Download Fallback
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
CLI Options
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 5Available CLI Flags
| 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) |
For CLI Applications
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
end
Don't forget System.halt/1 when your CLI finishes!
How ERTS Provisioning Works
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 reusePackage: Bundles your release + ERTS into a single compressed tarball
Compile: Rust dispenser embeds the payload and handles extraction at runtime
Build Environment Isolation
Batamanta version 1.4.0+ includes Batamanta.EnvCleaner, which automatically handles environment isolation during binary generation.
Why this matters
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
How it works
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.
ERTS Repository
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).
Troubleshooting
Linux: "ERTS not found" or wrong ERTS downloaded
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_64macOS: Binary doesn't run on older macOS versions
Ensure you're building with the correct deployment target:
batamanta: [
erts_target: :macos_12_x86_64 # or :macos_12_arm64
]Cross-compilation from macOS to Linux
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_64Alpine/musl: "Library not found"
Ensure 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 ...Architecture
- 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
Escript Support
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
]
]
end
The 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.
Testing
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 --helpLicense
MIT