Github actions for Elixir & Phoenix app with cache
You can already find some samples on how to configure Github actions for an Elixir/Phoenix project but I couldn’t find one which shows the full picture with cached dependencies to avoid losing time re-compiling your whole project deps on every push.
I’ll show you how to configure Github action to run the following tasks:
- Install & compile your project’s dependencies
- Run static code analysis such as
mix format
andcredo
anddialyzer
- Run your unit tests
- BONUS! Deploy to Gigalixir
Let’s dive in!
TL; DR: Skip toward the end to get the complete Github action manifest file ;)
Let’s start with a brand new Elixir app to play around with:
mix new my_app
cd my_app
Open the app with your favorite editor and let’s start to add our Github action steps one by one under .github/workflows/elixir.yml
. Our first goal is to install our project dependencies and compile them. We’ll talk about caching later.
Next, let’s run a simple static code analysis tool like mix format
by adding the following step next to the deps
one:
Notice that this step depends on the one above. We first need to install our project dependencies to be able to run mix format
.
Finally, let’s add a step to run our unit tests:
If you run this pipeline multiple times, you’ll notice that we are always re-installing and re-compiling our project’s dependencies. It’s time to add caching my friend.
Github action cache works by specifying a list of directories (v2) of files to cache using a given cache key. We’ll compute a hash based on the mix.lock
file to automatically invalidate the cache when our project’s dependencies change.
Since we’ve spread our pipeline into multiple jobs, you’ll need to fetch the cached directories at the beginning of each job.
Finally, if a cached version of our dependencies already exists, we’ll simply skip the job completely. Time is money!
Add the following cache steps after each setup
steps of our existing jobs:
Notice that we are also using the runner.os
matrix.otp
and matrix.elixir
built-in variables to generate a cache key which is unique across the OS, Erlang, and Elixir versions we use.
Don’t mind the priv/plts
folder for now.
Try to run our new pipeline a few times and you’ll notice that the deps
step is skipped completely when there are no dependency changes.
It’s time to run even more static code analyzers such as credo
and dialyzer
. Let’s add the following dev
and test
dependencies to our project in mix.exs
:
Don’t forget to run mix deps.get
. Let’s now add the commands to our CI pipeline under the static_code_analysis
step:
There is one final step for dialyzer
before we can run this new pipeline. We need to generate the PLT files, which can take a while and also needs to be invalidated when we update our project’s dependencies. PLT files are generated using the mix dialyzer —-plt
command and located under priv/plts
folder. This is why I made you add this folder to our cached directory list as well earlier.
Now, simply add the following commands to our “Install Dependencies” task in our deps
step:
Give our new pipeline a try!
Maybe you have noticed that old pipelines don’t stop automatically when you have a new one scheduled for your branch. You can use the following bonus step to cancel previous pipelines. You’ll have to add that step at the beginning of each job:
Finally, it’s time for the bonus step to deploy your app on Gigalixir! Simply add the following job at the end of the file:
Don’t forget to set up your Github action secrets and feel free to tune the env variables the way you want.
You can find here the complete CI pipeline using Github action for Elixir and Phoenix projects using Github action cache. I hope you’ve enjoyed reading this article as much as I deed writing it. Happy coding!