Elixir Automation Engineering via GitHub Actions

The implementation of Continuous Integration and Continuous Deployment (CI/CD) for Elixir projects on GitHub involves a sophisticated orchestration of the BEAM virtual machine, the Erlang runtime system, and the Elixir language. At its core, CI is a software development practice where developers frequently integrate code changes into a shared repository, allowing for the early identification and rectification of problems. In the context of Elixir, this means ensuring that the highly concurrent and distributed nature of the language is supported by a stable, reproducible environment. The goal is to automate builds and tests to assert the correctness of new code before it is merged into a main development branch, thereby accelerating software releases and improving overall software quality.

For an Elixir developer, achieving this automation requires a deep understanding of the dependency chain between the Erlang Open Source project (OTP) and the Elixir language. Because Elixir is compiled to run on the Erlang Virtual Machine (BEAM), the environment must be precisely configured to avoid version mismatches that can lead to catastrophic runtime failures or compilation errors. This is particularly critical when dealing with legacy projects, such as those associated with the Elixir in Action book series, which may require specific, older versions of Elixir and Erlang to maintain compatibility with the provided code samples.

Architectural Foundations of Elixir CI Pipelines

The structural basis of any GitHub Actions workflow for Elixir begins with the creation of a specific directory hierarchy in the root of the project repository. The system recognizes workflow definitions only when they are placed within the .github/workflows directory. Within this folder, developers define YAML files (such as ci.yml or elixir.yaml) that act as the blueprint for the automation process.

These YAML configurations define the triggers—the specific events that cause the workflow to execute. Common triggers include push events, where code is sent to the repository, and pull_request events, where a request is made to merge a feature branch into a target branch. For example, a robust pipeline is typically configured to run when changes are pushed to the main branch or when a pull request targets the main branch. This ensures that no code enters the primary production branch without passing a rigorous set of automated checks.

The execution environment is typically based on Ubuntu runners. The runs-on: ubuntu-latest directive instructs GitHub to provision a virtual machine with the latest stable version of Ubuntu, providing a clean slate for the installation of the BEAM ecosystem. A critical component of this setup is the management of environment variables. The MIX_ENV variable must be explicitly set to test within the workflow configuration. This ensures that the Mix build tool operates in the test environment, loading the appropriate configuration and dependencies required for the test suite.

BEAM Environment Setup and Version Management

The preparation of the Elixir environment is the most technical phase of the CI process. Because of the interdependence between Erlang/OTP and Elixir, simple installation is often insufficient; precise versioning is required.

There are several primary methods for setting up the environment:

  • erlef/setup-beam: This action is used to set up the BEAM environment. It is highly flexible and can utilize a .tool-versions file to determine the exact versions of Erlang and Elixir to install.
  • actions/setup-elixir: This action installs OTP and Elixir. However, it is important to note that this specific repository is currently unmaintained by GitHub and has been transitioned to the Erlang Ecosystem Foundation at erlef/setup-elixir.
  • mileschou/elixir-action: This is a third-party action that provides an environment based on the official Elixir image. It allows for the execution of commands via an args parameter, such as mix test.

The complexity of the OTP release version specification necessitates careful handling. To avoid parsing errors—where a version like 23.0 might be incorrectly interpreted as the integer 23—it is mandatory to specify versions as YAML strings. This ensures that the setup action resolves the version according to semantic versioning rules.

For developers utilizing the asdf version manager, the .tool-versions file becomes the single source of truth. When the erlef/setup-beam action is configured with version-file: .tool-versions and version-type: strict, it ensures that the CI runner mirrors the exact environment used by the developer locally.

Analysis of Elixir in Action Technical Requirements

The Elixir in Action series provides a pedagogical path through the language, and the associated code samples have evolved across different editions, requiring different environment specifications.

The requirements for the various editions are as follows:

Edition Elixir Version Erlang Version Required Tools
First Edition 1.0 17 Git Client
Third Edition 1.15 26.x Git Client, asdf (optional)

For the Third Edition, the presence of the .tool-versions file is critical. If a developer is using the asdf version manager, they must run asdf install in the root folder of the repository to align their local environment with the requirements. This consistency is mirrored in the CI pipeline; if the GitHub Action uses erlef/setup-beam with a strict version type, it will look for this file to ensure the code compiles under the intended Erlang 26.x and Elixir 1.15 environment.

Implementation of the CI Workflow

A complete CI pipeline involves a sequence of steps that move the code from a raw state to a validated state. The process begins with the actions/checkout@v4 step, which clones the repository into the runner's workspace. Without this step, the subsequent actions would have no access to the codebase.

Following the checkout, the BEAM environment is initialized. A typical configuration for a high-quality CI pipeline follows this sequence:

  1. Checkout: Clones the repository.
  2. Environment Setup: Uses erlef/setup-beam or erlef/setup-elixir to install the specific OTP and Elixir versions.
  3. Dependency Installation: Running mix deps.get to fetch all necessary libraries.
  4. Code Quality Checks: Running formatting checks (e.g., mix format --check-for-diff) to ensure the code adheres to style guidelines.
  5. Test Execution: Executing mix test to verify the logic of the application.

In advanced scenarios, such as Phoenix applications, the CI pipeline must also manage external services. This often involves setting up a Postgres DB service within the workflow to allow the test suite to perform database migrations and execute integration tests against a real database.

Specialized Automation Actions and Licensing

The Elixir ecosystem on GitHub includes various community-driven actions. One such example is the mileschou/elixir-action. This action simplifies the process by providing an image based on the official Elixir distribution.

The implementation of this action in a workflow looks like this:

yaml steps: - name: Elixir action uses: mileschou/elixir-action@master with: args: mix test

It is important to note that mileschou/elixir-action is not certified by GitHub. It is provided by a third party and is governed by separate terms of service and privacy policies. However, it is distributed under the MIT License, which provides a permissive framework for its use in both open-source and proprietary projects.

Deployment and Containerization Strategies

Beyond basic testing, a mature CI/CD pipeline extends into publishing and containerization. This involves transforming the validated code into a distributable format.

The pipeline can be expanded to include:

  • Hex Package Publishing: Automating the upload of the library to hex.pm upon a successful tag push.
  • Docker Image Construction: Building a container image that includes the compiled Elixir release. This usually involves a multi-stage Dockerfile where the first stage compiles the code (the build stage) and the second stage contains only the runtime artifacts (the release stage) to minimize image size.
  • Fly.io Integration: Using specialized guides to deploy the Elixir application to a cloud platform via GitHub Actions, ensuring that the deployment only happens if the test job completes successfully.

Detailed Workflow Example

A comprehensive workflow integrating these concepts would be structured as follows:

```yaml
name: Elixir CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

env:
MIX_ENV: test

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

  - uses: erlef/setup-beam@v1
    id: beam
    with:
      version-file: .tool-versions
      version-type: strict

  - name: Install dependencies
    run: mix deps.get

  - name: Run tests
    run: mix test

```

In this configuration, the permissions block is set to contents: read, which is a security best practice ensuring the workflow has only the minimum necessary access to the repository. The id: beam allows other steps to reference the outputs of the setup action if needed.

Conclusion

The integration of Elixir into GitHub Actions is a process of managing the delicate balance between the Erlang runtime and the Elixir language. By utilizing the .github/workflows directory and leveraging the erlef/setup-beam action, developers can create a deterministic environment that eliminates "it works on my machine" syndromes. The transition from the unmaintained actions/setup-elixir to the Erlang Ecosystem Foundation's tools highlights the importance of using current, community-supported infrastructure.

For those working with the Elixir in Action materials, the strict adherence to versioning—specifically Erlang 26.x and Elixir 1.15 for the Third Edition—is not merely a recommendation but a requirement for successful compilation. The use of .tool-versions files combined with asdf and GitHub Actions creates a seamless bridge between local development and remote automation. Ultimately, a well-constructed CI pipeline that encompasses checkout, strict BEAM setup, dependency resolution, and automated testing forms the backbone of professional Elixir development, ensuring that the high-concurrency guarantees of the BEAM are backed by a rigorous and automated verification process.

Sources

  1. Elixir Action GitHub Marketplace
  2. Elixir CI Testing, Publishing, and Containerization with GitHub Actions
  3. setup-elixir GitHub Repository
  4. Elixir in Action Code Samples
  5. Fly.io GitHub Actions for Elixir CI/CD
  6. Elixir in Action GitHub Branch

Related Posts