Journal
Migrating CI to GitHub Actions
I’ve grown frustrated with Heroku lately. They’re incredibly expensive considering the specs of their servers, the limitations of their infrastructure, and the price of the alternatives, but I’ve always given them the benefit of the doubt, telling myself that I pay for the peace of mind. Now that I experienced a serious database issue without hearing from Heroku for a week, I’m ready to move on. While it’ certainly take some time to move Cushion’s entire infrastructure to another platform, I can start with continuous integration.
I’ve known about GitHub Actions since it launched, but never took the time try it out. Knowing that I could handle CI under the same roof as my code and PRs, however, always sounded dreamy. So much so that as soon as it was announced, the thought of running CI anywhere else seemed strange. With my recent Heroku woes, I decided to make the time to try it out. I read the docs and learned that all I needed to do was configure a few YAML files within a .github/workflows
directory in my repo.
In Cushion’s main repo—currently housing the Ruby app and the frontend—I created YAML files for Ruby and Node. With two “workflows” instead of one, they’ll run in parallel, so my CI is already faster by that alone. The Ruby workflow actually spins up Postgres and Redis databases for its tests, so it takes a bit longer to get going. Because of that, the frontend unit tests often finish before the Ruby workflow even starts running the tests!
The beauty of GitHub Actions is that everything just seems to work as expected. With a crystal clear settings structure, I configure the services, list the steps to run, and see the results in my pull requests’ checks. One crucial part that doesn’t come built-in, however, is Slack notifications. A long time ago, I wrote about how I use Slack as a notification center with Cushion, so any CI updates or error alerts ping me in Slack. With GitHub Actions, I actually needed set up my own Slack bot and manually configure the notifications for CI. It might seem tedious, but it was actually pretty fun.
After browsing through a few options on the GitHub marketplaces, I landed on Vox Media’s action—Slack Notify Build. This one provides a simple configuration to create color-coded notifications without much effort. Because this action only sends a message, I actually needed to “litter” my workflow file with several triggers for various messages, like starting a build, successfully finishing a build, and when a build fails. GitHub Actions makes this super easy by providing an if
property on each step to check for success()
or failure()
.
Creating the Slack bot was slightly confusing using Slack’s dev portal, but worth it in the end. Since I’m the only one working on Cushion, I don’t have any people in the Slack—besides my bff James because AIM shut down. This made me think to use a person as the bot rather than yet another bot. Not giving it too much thought, I searched for folks with the initials “CI” and landed on Chris Isaak. I know it’s weird, but I’m also weird.
Once I got the main app running CI, I switched gears to Cushion’s invoice repo, which runs Nuxt.js and Node, but more importantly, handles end-to-end testing with Cypress, which I wrote about earlier. I really wanted to bake this into my CI with Heroku, but wasn’t entirely sure if it were possible, since I’d essentially need to spin up the Ruby app and databases, then start the Nuxt.js server. With GitHub Actions, however, this was no problem.
Similar to the Ruby app, I spun up Postgres and Redis databases again, but this time, I wrote a SQL file to seed the database, so Cypress had a few invoices to work with. The real beauty is that Cypress’s GitHub Actions documentation is so thorough that it was straightforward from start to finish. The only hiccups along the way were from user error, as I was a bit blurry-eyed after ~12 hours of “working” that day. Once I pointed Cypress’s build
property to my Nuxt.js build task and start
property to Nuxt.js’s start task, it worked.
The “one more thing” that blew me away with Cypress was its ability to upload screenshots and videos of the tests upon failure. GitHub Actions has an action for uploading artifacts, which I was able to use with the videos that Cypress generates. Using the conditional if
property on the steps, the action uploads the videos only on failure. If anything ever goes wrong, I don’t need to blindly troubleshoot and guess—I can simply check the tape.
I’m thrilled with making it this far, but this is only a first step. My longterm goal is to combine all of Cushion’s moving parts into a single monorepo, which isn’t easily possible with Heroku, but it is very possible on most other platforms, so that will be a plus of moving on. Also, once everything is in a monorepo, I can have all of my GitHub Actions workflows triggering together, with a single series of integration tests to run through. I simply can’t wait.