Release Train - Continuous Releases Made Easy

By Will Roden
August, 1 2023 | Reading time: 3 minutes

Release Train is a tool to automate creating a new release every time you merge a pull request. It is inspired by semantic-release, but is easier to set up and use. Add a step to your GitHub workflow, start labeling your pull requests, and you're riding the release train. No special incantations are required for your commit messages, and your repo can remain npm-free.

I've been a little jealous of the npm ecosystem since seeing semantic-release for the first time. At the time in the Go world, it was common to see issues requesting a new release so recent changes can be used downstream. I needed something like this.

I tried using semantic-release with a couple of my projects, but it wasn't a good fit. I didn't like being constrained to a specific commit message format, and I wasn't good enough with npm to be comfortable configuring it. As much as I liked the idea of semantic-release, it wasn't worth relearning a bit of npm every time I wanted to make a configuration change. Plus, package.json is out of place in a Go project. It's like Lollipop Man said, "I heard some funk with some main ingredients like Doobie Brothers ... It was cool, but can you imagine Doobie in your funk?". Semantic-release is cool, but can you imagine npm in your Go?

Having rejected the idea of signaling release types through commit messages, I started looking at other options. I realized the primary unit of change in my workflow is a pull request, not a commit. So, instead of looking at commits, my ideal tool would look at pull requests. Once you move to pull requests, you can use labels to indicate the change type.

My first go at this was semver-next. It decides the next version to tag based on either PR labels or Conventional Commit messages. I used semver-next combined with some release scripts on most of my projects for the next few years. It works all right but also has some limitations I don't like. It doesn't support prereleases or configurable labels. It also determines the previous release just by looking at the release labeled " latest release" on GitHub. This means it cannot support independent release branches such as v1 and v2.

I decided to start over instead of improving semver-next partially because I never liked the name semver-next and partially because I didn't like refactoring all that code I wrote when I was new to Go.

My goals for the new project were:

I also decided to drop support for Conventional Commit messages. In my years using semver-next, I used Conventional Commit messages maybe twice outside of testing semver-next itself. Instead, if I pushed a commit to main that I needed to release, I found myself creating a PR with an empty commit to force the release.

Release Train met all my goals, except maybe the name. It's probably better than semver-next, but not by much. I should have gone with Easy Peasy Releasy.