Saturday, December 20, 2025

Elixir reusable Github action (2025)

I realized I haven't written a new post in a while, so I wanted to share a small but useful piece of infrastructure I lean on a lot: the reusable GitHub Action I use for my Elixir projects. This is also the foundation of the CI/CD pipeline we use at Hiive.

TL;DR

The input variables already give a decent overview of what the action does, but I’ll walk through the important bits below.

Using the action

Using this action is intentionally simple. Most inputs can be omitted, which keeps your workflows short and focused:

By default, the action:

  • Installs Elixir and Erlang/OTP (using either explicit versions or )
  • Restores and populates dependency and build caches
  • Installs Hex/Rebar
  • Compiles your dependencies and app

You can selectively turn pieces off via inputs like , , or when jobs need a lighter setup.

Caching

The first thing you’ll probably notice is the input. By default, this uses the GitHub job name. That value is suffixed onto all cache keys, but the action also allows restoring from other cache names.

This gives you:

  • Per-job isolation – different jobs (e.g. tests, lint, dialyzer) can have their own dependency and build caches, which matters when tools compile with different options.
  • Cross-job reuse – restore keys still allow you to fall back to more general caches, reducing cold starts while keeping thrashing low. The tradeoff is higher total cache usage.

The action also exposes two helpful outputs:

You can wire these into later steps to, for example, skip expensive work on a warm cache hit.

Finally, when a job is re-run, the action takes a "clean slate" approach: it restores the cache, explicitly cleans compiled artifacts and dependencies, and only then rebuilds and saves a fresh cache. That keeps reruns more deterministic and helps reduce flaky behavior tied to stale build artifacts.

In practice, this has made it much easier for developers to re-run failed jobs and debug them without fighting mysterious cache issues.

Versioning

For versions, the action leans on your file whenever possible. If you’re not already using mise, I highly recommend it. Even without mise, keeping a file as the single source of truth for language versions is a big win.

The action will:

  • Read and versions from if explicit inputs aren’t provided.
  • Export the exact resolved and as outputs, so downstream jobs can log or act on the precise versions in use.

That keeps GitHub Actions, local development, and other environments in sync, and avoids the usual "works on my machine" version drift or version bump PRs that touch 30 different files.