Tinfoil generates a GitHub Actions workflow that cross-compiles a Burrito-based Elixir CLI and ships it as a GitHub Release on every tag. This guide covers the minimum you need to get to a working release.
For a full worked example, see the
tinfoil_demo
repo.
Add the dependency
Add :tinfoil alongside :burrito in mix.exs:
def deps do
[
{:burrito, "~> 1.0"},
{:tinfoil, "~> 0.2", runtime: false}
]
endDon't set
only: :dev. The generated CI workflow runsMIX_ENV=prod mix tinfoil.build, so tinfoil must compile in the prod environment too.runtime: falsekeeps it out of the started applications at runtime while still making the mix tasks available during builds.
Configure
Add a :tinfoil key to project/0:
def project do
[
app: :my_cli,
version: "0.1.0",
# ...
tinfoil: [
targets: [:darwin_arm64, :darwin_x86_64, :linux_x86_64, :linux_arm64],
homebrew: [enabled: true, tap: "owner/homebrew-tap"],
installer: [enabled: true]
]
]
endThe only required key is :targets. Every other knob has a sensible
default, covered in the
configuration reference.
You also need a Burrito :releases block. Tinfoil resolves its
abstract target atoms against your Burrito target names by matching
[os:, cpu:] pairs, so the names inside your :releases block can
be anything; see targets and runners for how resolution
works.
Your CLI needs an Application callback
Burrito's main_module config key is metadata only. Burrito boots
the BEAM but never calls main/1 itself. Without an OTP application
callback that reads argv and runs your CLI, the binary launches and
hangs until you SIGTERM it. The minimal pattern:
# mix.exs
def application do
[extra_applications: [:logger], mod: {MyCli.Application, []}]
end
# lib/my_cli/application.ex
defmodule MyCli.Application do
use Application
def start(_type, _args) do
if Burrito.Util.running_standalone?() do
spawn(fn ->
MyCli.run(Burrito.Util.Args.argv())
System.halt(0)
end)
end
Supervisor.start_link([], strategy: :one_for_one, name: MyCli.Supervisor)
end
endThe running_standalone?/0 guard keeps mix test and iex -S mix
from hijacking their own argv.
Generate the workflow
mix deps.get
mix tinfoil.init
On a fresh mix new project, you can skip the manual edits entirely:
mix tinfoil.init --install # splices dep + starter config into mix.exs
mix tinfoil.init # generates the workflow + supporting files
Generated layout:
your-project/
├── .github/workflows/release.yml ← CI pipeline (always)
├── .tinfoil/formula.rb.eex ← if homebrew enabled
├── scripts/
│ ├── install.sh ← if installer enabled (Unix)
│ └── install.ps1 ← if installer enabled (Windows)
└── mix.exsCommit the generated files.
Ship a release
git tag v0.1.0
git push --tags
The push fires .github/workflows/release.yml, which builds every
target in parallel, packages each binary with its sha256, and
publishes a GitHub Release with every archive attached. If you
enabled Homebrew or Scoop, the tap and bucket repos are updated in
the same run.
Automating version bumps
Tinfoil's generated workflow only reacts to tag pushes. A typical next step is adding release-please to open rolling release PRs driven by conventional commits, so tagging becomes "merge the release PR."
The wiring has a couple of GitHub-specific auth gotchas, documented in Automatic releases with release-please.
Next
- Configuration reference -- every option in the
:tinfoilkeyword. - Targets and runners -- how abstract targets map to Burrito targets and GitHub runners.
- Distribution -- Homebrew, Scoop, installers, prerelease handling.
- Mix tasks -- task-level reference.