Create a distributable Elixir executable using Bakeware


What follows is a step-by-step guide to set up an Elixir project so that it produces an executable that can be distributed. The executables will run on other Linux machines with the same processor architecture.

We will be using Bakeware.

The Project

I've created an example project

mix help bakeware_example

It's here on GitHub.

Add the Dependency

Add the following to mix.exs

defp deps do
    {:bakeware, runtime: false}

We set `runtime: false` as the dependency is not part of the runtime application (See the Mix doc for a full explanation).

Update dependencies

mix deps.get

Configure the Release

Add release details to mix.exs:

  @app :bakeware_example

  def project do
      app: @app,
      releases: [{@app, release()}],
      preferred_cli_env: [
        release: :prod


  defp release do
      overwrite: true,
      quiet: true,
      steps: [:assemble, &Bakeware.assemble/1],
      strip_beams: Mix.env() == :prod

Create the First Release

We can now create a first executable to see where we're at

$ MIX_ENV=prod mix release
Bakeware successfully assembled executable at:


Running the executable works, but doesn't produce any output

$ _build/dev/rel/bakeware/bakeware_example

Implement main

We need to implement a function that will get called when the application starts.

Create lib/bakeware_example/cli.ex

defmodule BakewareExample.CLI do
  use Bakeware.Script

  @impl Bakeware.Script
  def main(_args) do
    IO.puts "bakeware_example works!"

Configure that as the startup module in mix.exs

--- mix.exs
+++ mix.exs
@@ -16,7 +16,8 @@ def project do
   def application do
-      extra_applications: [:logger]
+      extra_applications: [:logger],
+      mod: {BakewareExample.CLI, []}

Now when be rebuild and run, we get some output

$ MIX_ENV=prod mix release
$ _build/dev/rel/bakeware/bakeware_example
bakeware_example works!

We're not Finished!

Now, if I copy that executable onto a computer with a recent Linux distribution, it will work.

But, if the distribution is a bit older, it fails:

$ ./bakeware_example 
./bakeware_example: /lib/x86_64-linux-gnu/ version `GLIBC_2.34' not found (required by ./bakeware_example)

The problem is that our executable relies on a more recent version of glibc.

The solution, as explained in the Bakeware README is to build with an older version of glibc.

A Containerized Build

So, let's use a slightly older Linux version in a container to build the executable.

What follows uses Podman but should work exactly the same with Docker.

# Build releases with a recent Elixir, but using an older Linux
# so the executable depends on an older
# (and more widely available) GLibC version.

FROM debian:buster-slim

# Installing the dependency packages takes 90% of the time
# so we do it first to allow quick builds of images
# with varied options.

  apt-get update && \
  apt-get install -y curl libncurses5 procps build-essential zstd unzip locales

# Set up workspace

# Environment for Erlang
ENV ERTS_PLATFORM=debian~buster_amd64

# Environment for Elixir
ENV PATH="${PATH}:/app/elixir/bin"

# Set up Erlang and Elixir
  curl -O $ERLANG_URL/erlang-base_$PACKAGE && \
  curl -O $ERLANG_URL/erlang-ssl_$PACKAGE && \
  curl -O $ERLANG_URL/erlang-crypto_$PACKAGE && \
  curl -O $ERLANG_URL/erlang-public-key_$PACKAGE && \
  curl -O $ERLANG_URL/erlang-asn1_$PACKAGE && \
  curl -O $ERLANG_URL/erlang-syntax-tools_$PACKAGE && \
  curl -O $ERLANG_URL/erlang-inets_$PACKAGE && \
  curl -O $ERLANG_URL/erlang-mnesia_$PACKAGE && \
  curl -O $ERLANG_URL/erlang-runtime-tools_$PACKAGE && \
  dpkg -i *.deb && \
  echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \
  locale-gen && \
  mkdir elixir && \
  curl --output elixir/$ELIXIR_ZIP$ELIXIR_ZIP && \
  (cd elixir; unzip $ELIXIR_ZIP) && \
  mix local.hex --force && \
  mix local.rebar --force

# Install Elixir Dependencies
COPY ../mix.* /app/
RUN mix deps.get

COPY ../lib /app/lib/

We've kept the ELIXIR_VERSION as a build argument, so we can make it match the project more easily.

$ podman build --file Containerfile --env ELIXIR_VERSION=1.15.5 --tag bakeware_example:latest .
Successfully tagged localhost/bakeware_example:latest
$ podman run -v `pwd`:/app/_build/prod/rel/bakeware -e MIX_ENV=prod bakeware_example:latest mix release
Bakeware successfully assembled executable at:


Because we set up a volume pwd:/app/_build/prod/rel/bakeware the executable is actually build in the project root.

It Works!

Running it locally works

> uname -a
Linux framework 5.19.0-43-generic #44~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon May 22 13:39:36 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
> ./bakeware_example 
bakeware_example works!

As does running it on an older machine

> uname -a
Linux foobar 5.4.0-107-generic #121-Ubuntu SMP Thu Mar 24 16:04:27 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
> ./bakeware_example 
bakeware_example works!