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

1 Like

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.

2 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:

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

2 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.

you can use this tutorial…