Batamanta Mascot

Package your Elixir applications as 100% self-contained executables. No Erlang/Elixir installation required on the target machine.


Hex VersionHexDocsCI StatusSelf-containedERTS Embedded


Features


Requirements

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 libsixel

Linux

# Ubuntu/Debian
sudo apt install libsixel-tools

# Arch Linux
sudo pacman -S libsixel

# Fedora
sudo dnf install libsixel

Terminal 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}]
end

2. 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
    ]
  ]
end

Configuration 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 musl

OTP 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 batamanta

Version Resolution

In auto mode, if the exact version is not available:

  1. Tries OTP-28.0 first (most common)
  2. Then OTP-28.1, OTP-28.2, etc.
  3. 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


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_64

Problem: 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/erlang

Problem: 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-gnu

Build with explicit target:

mix batamanta --erts-target ubuntu_22_04_x86_64

How libc Detection Works

Batamanta uses multiple methods in order:

  1. ldd --version - Most reliable, checks output for "musl" or "glibc"
  2. Dynamic loader files - Checks /lib/ld-musl-*.so vs /lib64/ld-linux-*.so
  3. /etc/os-release - Checks ID=alpine, ID=void, etc.
  4. /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:

For production self-contained binaries:

  1. Ensure network access during build
  2. Use specific ERTS version: batamanta: [otp_version: "26.2.5"]
  3. 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 5

Available 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

  1. Auto-detection: Batamanta detects your host platform using:

    • :os.type() for OS identification
    • :erlang.system_info(:system_architecture) for architecture
    • ldd --version for libc detection on Linux (glibc vs musl)
  2. Download: Fetches pre-compiled ERTS from Hex.pm builds or from the Batamanta ERTS Repository

  3. Cache: Stores in ~/.cache/batamanta/ for reuse

  4. Package: Bundles your release + ERTS into a single compressed tarball

  5. 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:

How it works

When you run mix batamanta, the tool:

  1. Detects and filters out version manager paths from the PATH.
  2. Sanitizes the environment to include only essential system variables.
  3. (In Escript mode) Prepends the downloaded ERTS bin directory to the PATH during 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:

Batamanta ERTS Repository

This repository hosts pre-compiled Erlang Run-Time System (ERTS) binaries for:

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_64

macOS: 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-gnu

Then build:

mix batamanta --erts-target ubuntu_22_04_x86_64

Alpine/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

  1. Detect: Auto-detect or resolve manual target configuration
  2. Fetch: Download ERTS from Hex.pm builds
  3. Release: Compile your Elixir code with mix release
  4. Package: Bundle release + ERTS with Zstd compression
  5. Compile: Build Rust dispenser that embeds the payload
  6. 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
end

Note: 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 --help

License

MIT