Skip to main content

README.md

# ReleaseKit

ReleaseKit builds deployment-neutral artifacts from Mix OTP releases.

It is for Elixir applications that want a repeatable build product without tying
that build product to a deploy tool. ReleaseKit produces ordinary files:

- an OTP release tarball;
- an ETF manifest describing the tarball, runtime command, runtime environment
  hints, and optional HTTP health check.

Deployment tools such as HostKit can consume the manifest, but ReleaseKit does
not know about systemd, users, Caddy, hosts, or filesystem layouts.

## Why use ReleaseKit?

A deploy pipeline usually needs two separate responsibilities:

1. **Build an application artifact** from source.
2. **Install and supervise that artifact** on a host.

ReleaseKit handles only the first responsibility. It gives downstream deploy
systems a small, stable manifest instead of asking every application to invent a
custom release tarball format or wrapper Mix task.

## Installation

```elixir
def deps do
  [
    {:release_kit, "~> 0.1.1", only: [:dev, :prod], runtime: false}
  ]
end
```

## Quick start

Build a production artifact for the default Mix release:

```sh
MIX_ENV=prod mix release_kit.artifact --out-dir _build/prod/artifacts
```

For a web service, record health-check metadata:

```sh
MIX_ENV=prod mix release_kit.artifact \
  --out-dir _build/prod/artifacts \
  --port 4000 \
  --health-path /
```

The output names are stable for deployment tooling:

```text
_build/prod/artifacts/my_app-20260620-abcdef0.tar.gz
_build/prod/artifacts/my_app.etf
```

## Configuration

You can put artifact defaults in application config and keep the command short:

```elixir
# config/config.exs
config :release_kit, :artifact,
  port: 4000,
  health_path: "/",
  env_clear: %{
    "MY_APP_WEB" => "true",
    "MY_APP_PORT" => "4000",
    "RELEASE_DISTRIBUTION" => "none"
  }
```

Then build with:

```sh
MIX_ENV=prod mix release_kit.artifact --out-dir _build/prod/artifacts
```

CLI flags override config values when provided.

## Prebuild steps

Some applications need generated files inside the OTP release, such as frontend
assets. Configure prebuild steps and still use the same ReleaseKit task:

```elixir
config :release_kit, :artifact,
  port: 4000,
  health_path: "/",
  prebuild: [
    {ReleaseKit.Step.Volt, root: "assets", production: true, frozen: true}
  ]
```

A step is any module that implements the `ReleaseKit.Step` behaviour:

```elixir
defmodule MyApp.ReleaseStep do
  @behaviour ReleaseKit.Step

  @impl true
  def run(opts) do
    # build generated files before mix release runs
    :ok
  end
end
```

`ReleaseKit.Step.Volt` is compiled only when both optional dependencies `:volt`
and `:npm` are available in the consuming project. It installs locked npm
packages from the configured asset root, removes Volt's output directory, and
runs `mix volt.build` before the OTP release is assembled.

See `examples/vanilla` for a minimal Phoenix/Volt app that builds with plain:

```sh
MIX_ENV=prod mix release_kit.artifact --out-dir _build/prod/artifacts
```

## Manifest shape

The manifest is an ETF-encoded map:

```elixir
%{
  tool: "release_kit",
  format: :beam_release_artifact,
  format_version: 1,
  app: "my_app",
  release: "my_app",
  version: "20260620-abcdef0",
  mix_env: "prod",
  tarball: "/absolute/path/to/my_app-20260620-abcdef0.tar.gz",
  runtime: %{command: ["bin/my_app", "start"]},
  env: %{
    clear: %{"MY_APP_WEB" => "true"},
    secret: []
  },
  health_check: %{
    path: "/",
    port: 4000,
    url: "http://127.0.0.1:4000/"
  }
}
```

## Task options

```text
--out-dir PATH       Directory for the tarball and manifest
--release NAME       Mix release name; defaults to the app name
--version VERSION    Artifact version; defaults to YYYYMMDD-gitsha
--port PORT          HTTP port recorded in health-check metadata
--health-path PATH   HTTP path recorded in health-check metadata
--skip-release       Package an existing _build/.../rel release directory
--skip-prebuild      Do not run configured prebuild steps
```

## Deployment

ReleaseKit intentionally stops after producing files. A deployment system should
read the manifest, unpack the tarball, write environment files, supervise the
release, and run readiness checks.