ShelllessRelease

Shell-free, distribution-hardened mix release for distroless images.

mix release emits a layered set of #!/bin/sh scripts as its launch interface (bin/<name>releases/<v>/elixirerts-*/bin/erl), all of which ultimately exec the native erlexec. On a distroless :nonroot image there is no /bin/sh, so those scripts cannot run.

ShelllessRelease replaces them with a single native launcher (compiled from the bundled priv/launcher/start.c) that execves the BEAM directly and pre-starts distribution itself, so the image needs no shell. The launcher is a multi-call binary installed over the existing bin/<name> entry points, preserving the interface so nothing downstream changes.

On top of being shell-free, it hardens the release:

Installation

Add shellless_release to your deps in mix.exs:

def deps do
[
{:shellless_release, "~> 0.1"}
]
end

Usage

Add the hardening step to your release in mix.exs:

def project do
[
# ...
releases: [
my_app: [
steps: [:assemble, &ShelllessRelease.harden/1]
]
]
]
end

Configure it (all keys are optional):

config :shellless_release,
# Zero-arg task verbs -> compiled-in expressions (the whitelist). These
# replace `bin/<verb>` shell overlays; each runs non-distributed.
tasks: [
migrate: "MyApp.Release.migrate()",
seed: "MyApp.Release.seed()"
],
# Pinned Erlang distribution port (build-time constant; default 24369).
dist_port: 24369,
# Require TLS distribution + the cert bundle (default true). When false,
# distribution is cookie-only (only sensible for non-clustered apps).
require_tls: true,
# EPMD-less distribution (default true). Requires one node per IP.
epmdless: true,
# Extra entry-point names to install the launcher at, beyond "server" and
# the task verbs (rarely needed).
extra_entry_points: [],
# Remove the generated shell launchers after install (default true).
strip_shell_scripts: true

The launcher is compiled by this step duringmix release, which is expected to run in your Dockerfile's builder stage where a C compiler (cc/gcc) is present. The library itself ships only C source — it never compiles anything at deps.compile time.

At runtime the entry points behave exactly like a stock release:

bin/server # boot the application
bin/migrate # run a whitelisted task verb (non-distributed)

Requirements

Documentation

Full documentation is on HexDocs. Start with the ShelllessRelease moduledoc for the launcher, and ShelllessRelease.EpmdLess for EPMD-less distribution.

License

MIT — see LICENSE.