Anyone in the Elixir community running Github actions / act successfully?

Hi all, not an Elixir specific question, but hoping for a favourable signal to noise ratio if I ask it here.

In theory it is possible to run a Github Action locally, for example before performing a git push to pushing to their server. There are many potential reasons to do so, reduced context switching, reducing failed build noise, etc.

Anyhow, the project GitHub - nektos/act: Run your GitHub Actions locally 🚀 appears to offer this functionality, the idea being, it’s executable runs your github workflow on a local docker image. However, in practice, it seems to be downloading 40GB of container layers every time I run a workflow.

I’m just wondering, before going on the deep dive, is anyone in the Elixir community actually running it successfully, or if not, is there a working fork, or, has anyone used anything similar to run github actions locally, or should I just move on and accept this is a waste of time?

Thanks

2 Likes

You could always run git hooks locally, they are quite flexible and easy to set up and don’t depend on github.

1 Like

Oh, indeed, but I’m looking for fast cycle debugging of actual github actions (mostly erlef/setup-beam@v1 stuff, but also some occasional weirdness around coveralls (paths) and stuff like that. I’m not just looking to run mix test or the basic stuff.

3 Likes

You could use dagger.

2 Likes

Or earthly.

2 Likes

Interesting, so the suggested optimisation around GitHub clunky CI / lack of local reproducibility seems to be to add a portable CI independent wrapper which can be run locally, in GitHub CI, or presumably on the providers cloud build system. I can see the attraction, particularly as many teams seem to be jumping back and forth between GitHub, Gitlab, Google, etc.

1 Like

You could use Earthly or Dagger to encapsulate the Pipeline inside the container and then in CI (GitHub, GitLab, etc.) just invoking your code and injecting necessary configuration and secret.

I recommended Dagger personally if you have a plan to scale the usage through your organization since it support runner on kubernetes. And it can be run your pipeline code with the Elixir SDK. :slight_smile:

2 Likes

I’m using act to run my Github actions locally. I was having an issue with missing OpenSSL libraries, which caused rebar to fail. Forcing the runner to use ubuntu-22.04 instead of ubuntu-latest resolved my issue.

My workflow is based off the “golden standard” and now looks like this:

name: Elixir CI

# Define workflow that runs when changes are pushed to the
# `main` branch or pushed to a PR branch that targets the `main`
# branch. Change the branch name if your project uses a
# different name for the main branch like "master" or "production".
on:
  push:
    branches: ["main"] # adapt branch for project
  pull_request:
    branches: ["main"] # adapt branch for project

# Sets the ENV `MIX_ENV` to `test` for running tests
env:
  MIX_ENV: test

permissions:
  contents: read

jobs:
  test:
    # Set up a Postgres DB service. By default, Phoenix applications
    # use Postgres. This creates a database for running tests.
    # Additional services can be defined here if required.
    services:
      db:
        image: timescale/timescaledb-ha:pg16
        ports: ["5432:5432"]
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    runs-on: ubuntu-22.04
    name: Test on OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
    strategy:
      # Specify the OTP and Elixir versions to use when building
      # and running the workflow steps.
      matrix:
        otp: ["26.2.3"] # Define the OTP version [required]
        elixir: ["1.16.1"] # Define the elixir version [required]
    steps:
      # Step: Setup Elixir + Erlang image as the base.
      - name: Set up Elixir
        uses: erlef/setup-beam@v1
        with:
          otp-version: ${{matrix.otp}}
          elixir-version: ${{matrix.elixir}}

      # Step: Check out the code.
      - name: Checkout code
        uses: actions/checkout@v4

      # Step: Define how to cache deps. Restores existing cache if present.
      - name: Cache deps
        id: cache-deps
        uses: actions/cache@v4
        env:
          cache-name: cache-elixir-deps
        with:
          path: deps
          key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
          restore-keys: |
            ${{ runner.os }}-mix-${{ env.cache-name }}-

      # Step: Define how to cache the `_build` directory. After the first run,
      # this speeds up tests runs a lot. This includes not re-compiling our
      # project's downloaded deps every run.
      - name: Cache compiled build
        id: cache-build
        uses: actions/cache@v4
        env:
          cache-name: cache-compiled-build
        with:
          path: _build
          key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
          restore-keys: |
            ${{ runner.os }}-mix-${{ env.cache-name }}-
            ${{ runner.os }}-mix-

      # Step: Conditionally bust the cache when job is re-run.
      # Sometimes, we may have issues with incremental builds that are fixed by
      # doing a full recompile. In order to not waste dev time on such trivial
      # issues (while also reaping the time savings of incremental builds for
      # *most* day-to-day development), force a full recompile only on builds
      # that are retried.
      - name: Clean to rule out incremental build as a source of flakiness
        if: github.run_attempt != '1'
        run: |
          mix deps.clean --all
          mix clean
        shell: sh

      # Step: Download project dependencies. If unchanged, uses
      # the cached version.
      - name: Install dependencies
        run: mix deps.get

      # Step: Compile the project treating any warnings as errors.
      - name: Compiles without warnings
        run: mix compile --warnings-as-errors

      # Step: Check for unused dependencies.
      - name: Unused dependencies
        run: mix deps.unlock --check-unused

      # Step: Check Hex dependencies that have been marked as retired
      # by the package maintainers.
      - name: Hex retiremefnts
        run: mix hex.audit

      # Step: Check for security vulnerabilities within dependencies.
      - name: Mix Audit security vulnerabilities
        run: mix deps.audit

      # Step: Check that the checked in code has already been formatted.
      # This step fails if something was found unformatted.
      - name: Check formatting
        run: mix format --check-formatted

      # Step: Execute the tests.
      - name: Run tests
        run: mix test

5 Likes

Do you like it? Do you feel like it replicates the real thing well enough?

I’ve been hesitating to try it. Feedback would help.

1 Like

you can use this tutorial…

1 Like

Update, 1 year later, running Dagger, loving it, decided to code the CI in Golang, although there is an experimental elixir binding, loving it.

4 Likes

I noticed you started the Elixir SDK for Dagger. Any plan to take it out of experimental stage?

Is this dagger.io ?

I hate what AI has done to the internet.
Opened it to read what I presumed would be a nice pipelines build tool and got five pages of examples to build «agentic software» (open PRs for you based on some LLM crap etc).

We were blocked on a deploy due to gh actions not being able to npm install the other day and it got me motivated to look into more locally reproduceable builds.

Edit: buildkite is what I have had on my TODO-list to look at for the record

1 Like

Yeah, Dagger.io - used it on my last project. It was great.

What I like about it is how it’s pushing secrets management and private project dependencies as first-class citizens, as it’s such a pain to get that stuff working in GitLab, as so many less experienced folk see no issue with a separate local build config and a remote build config full of GitHub/GitLab/CircleCI specific seas of platform-specific YAML.

Another nice thing about Dagger.io is that they make it very easy to debug the build (interactive console) so you don’t need to get into 20-minute “change YAML, trigger build event, view the logs” hell cycles.

The AI/LLM hype at the moment is so jading; lots of it’s garbage, and every company under the sun is trying to convince investors they have their own HAL 9000 system when they’ve just got an even worse interface to their systems than the janky React (insert FANG company the developer wished they were working at when they selected the tech) system that preceded it.​​​​​​​​​​​​​​​​

2 Likes

Like how it is better? Gitlab has nothing special for dealing with that, especially when you have your custom runners, it’s all about that runner machine having the right keys to access that private stuff.

It should be within this year.

There is a lot of works to do (documentation, api polish, integration tests, catch up features, etc). But you can start using it and give a feedback to me (and the community). :pray:

1 Like

Dagger.io’s primary win (if you fully commit to it) is having composable code that works for both build and CI environments, offers cacheability, and is easy to debug (locally or even running on CI infrastructure)… While GitHub has supported secrets and SSH mounting for many years—allowing a single Dockerfile to serve both local and cloud needs—GitLab is somewhat lacking in this area.

GitLab doesn’t natively support mounting SSH secrets without manually configuring Docker-in-Docker, which often leads developers to simply copy and paste GitLab configurations rather than getting a Dockerfile working for both desktop and CI use, resulting in configuration divergence. When working with private dependencies, using your desktop Dockerfile to build CI images becomes particularly cumbersome in GitLab.

I’ve worked with the Go bindings rather than the Elixir language binding, as I found the former straightforward to understand and implement. I hope to avoid being drawn into a debate about the comparative merits and challenges of these platforms—GitLab certainly has many commendable features (plantuml support is excellent for example, as is the lack of Microsoft ownership) despite the specific limitations I mentioned.​​​​​​​​​​​​​​​​

1 Like

Yeah, I run Act on macos with docker, great for testing Actions before pushing them. Although I’ve been looking into something different like https://tart.run to avoid docker completely.

2 Likes

Looks neat but I have no clue why it’s Apple Silicon specific. :017:

It’s uses the new virtualization framework that was launched with Apple Silicon. It’s not a replacement for docker, since it only works for Mac, but I like to avoid docker completely and just run as close to what my servers runs.

1 Like