I almost didn't write this post (which was originally titled "GitHub Actions and your Elm web application") because during my research I realized that Dillon and Jeroen did an amazing job discussing GitHub Actions on the Elm Radio Podcast. As a result, I had to rethink what else I could usefully add to the discussion. Around 00:28:21 they started to talk about the relationship between the checks that you have in your local environment versus the CI checks. In particular, at about 00:31:10, Jeroen mentions:
I really like having the CI do the same thing as what I would do locally. Or said another way, I would really like to do locally what I do in CI.
I feel the same way. However, they didn't provide any practical or compelling solutions for achieving that. So in this post we are going to discuss that very topic, i.e. how to set up your local environment and your CI environment so that you can do the same things in both without any hassle.
Enter Nix
Nix is a tool that allows you to make isolated, reproducible development environments in a declarative way. I previously shared how I use Nix in my Elm projects so you can read that post to learn more. However, Nix has a steep learning curve and for most Elm projects you won't need the full power of Nix. That's why I recommend Devbox.
Devbox
Devbox is a wrapper around Nix that makes Nix user friendly. I also previously shared how I use Devbox in my Elm projects so you can read that post to learn more. Other people have written great introductions to Devbox so I won't repeat it here. Upgrade your Development Environments with Devbox is a great read. If you prefer to watch a video, I recommend Nix for Everyone: Unleash Devbox for Simplified Development.
Let's proceed with an example.
Example: L-System Studio
L-System Studio is an interactive web application built with Elm that's designed to help users explore and visualize L-systems. I picked this project since it doesn't have a good build, test, and deploy workflow either locally or in CI.
Let's fix that. Here are the changes I made:
-
Start using Devbox
- Install Elm via Devbox and configure a few environment variables for the Devbox shell. In particular, I put the
bin
directory on thePATH
so that when you're in the shell you can call your scripts by name.
- Install Elm via Devbox and configure a few environment variables for the Devbox shell. In particular, I put the
- Restructure and update the build script
-
Add a deployment script
- I use the same approach I explained in How I host Elm web applications with GitHub Pages.
- Add
elm-format
- Add
elm-test
and start testingLSys.generateSequence
-
Add a script to run various checks
- The script checks that all your Elm files are formatted correctly, your project builds, and your tests pass. You can add other checks as you need them, for e.g. an
elm-review
check.
- The script checks that all your Elm files are formatted correctly, your project builds, and your tests pass. You can add other checks as you need them, for e.g. an
-
Add a GitHub Action to deploy the project after running the checks
- The workflow triggers on every push to the
main
branch but you can also trigger it manually. It does a checkout of the repository, installs Devbox, runs various checks, and once everything succeeds it does a deploy.
- The workflow triggers on every push to the
The project structure before
The project structure after
How to use the project now
Locally you can enter the Devbox shell and run the scripts in the bin
directory.
devbox shell
# Build
build
# Test
elm-test
# Run checks
check
# Deploy
deploy
In CI, you can run the same scripts using devbox run
.
# Build
devbox run build
# Test
devbox run elm-test
# Run checks
devbox run check
# Deploy
devbox run deploy
Here's the deploy workflow:
name: "Deploy"
on:
push:
branches:
- 'main'
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # To access the gh-pages branch
- name: Install devbox
uses: jetify-com/devbox-install-action@v0.11.0
with:
enable-cache: 'true'
- name: Run checks
run: devbox run check
- name: Deploy
run: |
git config user.name ${{ github.actor }}
git config user.email devs@example.com
devbox run deploy
Even if you don't understand what's going on the main thing I want you to notice is that running your scripts in CI is not significantly different from running them locally.
Conclusion
Nix or Devbox can give you isolated, reproducible development environments that are as easy to use in CI as they are to use on your local machine. Thanks to those tools, working in CI doesn't have to be any harder than or different from working on your project locally. They provide a wonderful solution, just give them a try.
Further reading
- Luc Perkins from Determinate Systems makes a compelling case for using Nix in your GitHub Actions.
- Using Devbox in CI/CD with GitHub Actions
Bonus: Some notes on GitHub Actions
In preparation for the article I didn't end up writing I took some notes and found links that were helpful to me that may also be helpful to others.
- What is GitHub Actions?
- It makes it easy to automate your software workflows.
- Build, test, and deploy your code right from GitHub.
- Kick off workflows on any GitHub event to automate your tasks.
- The actions marketplace.
- Actions of interest:
- actions/checkout
- actions/cache
- actions/deploy-pages
-
Deploy to GitHub Pages
-
git.ts
- Read to help you improve your own deployment script.
-
- jetpack-io/devbox-install-action
- Pricing
- Free for public repositories.
- Pay-as-you-go pricing.
- Learn about storage and minutes.
- GitHub Free
- 500 MB
- 2,000 minutes per month (resets)
- GitHub Free
- Learn about pricing.
- Standard runner, Linux 2-core, USD$0.008 per minute
-
GitHub Actions documentation
-
Understanding GitHub Actions
- GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline.
- Terminology: workflow, event, jobs, runner, steps, action.
- Workflows can be triggered by an event, manually, or at a defined schedule.
- They are defined in
.github/workflows
in YAML files (.yml
). Use any name. - YAML cheatsheet
- They are defined in
- A job is a set of steps in a workflow that is executed on the same runner.
- Each job runs in its own virtual machine runner, or inside a container.
-
Understanding GitHub Actions
-
Standard GitHub-hosted runners for public repositories
- What's in provided in Ubuntu 24.04?
- Workflows
- Starter Workflows
- You can access context information in workflows and actions.
- Syntax
- Expressions
- What other Elm projects use GitHub Actions?
-
GitHub Actions on Discourse
- One of the major takeaways for me after reading through these Discourse posts is that using Devbox can help solve most (if not all) of the problems people have mentioned that they are running into. Devbox gives you a uniform view of the system whether it is your local machine or your CI/CD machine. If it works locally then you can expect it to work the exact same way remotely.
- Learn more about caching.
Subscribe to my newsletter
If you're interested in improving your skills with Elm then I invite you to subscribe to my newsletter, Elm with Dwayne. To learn more about it, please read this announcement.
Top comments (0)