At the moment of writing, our ecosystem spans several major applications that are crucial for the day-to-day operations of the company.

The list not only includes Now CLI and Now Desktop (which, along with our site, represents the most important entry points to our platform), but also projects like Hyper and Next.js - which eliminate all of the primary pain points you come across when building something online.

Summa summarum: They're all equally important to us and we love working on them and pushing all of them into the direction of a even brighter future with the help of our community. 🤗

That's also the reason why we're very excited whenever there's the time for a new feature or release: It's just a really great feeling to ship! 🤤

However, as the number of people that are using these apps keeps growing exponentially, a decision had to be made in order to preserve the stability of the code we actually hand out into the world - something that would ensure no friction is caused when publishing updates.

As a result, we've decided to start publishing Canary Releases.

In the following sentences, I'll talk about how we actually implemented this into three of our apps: Now CLI, Now Desktop and Hyper. If you haven't read the article about how to enable it yet, I highly recommend to do so first.

#Now CLI

Just like the stable ones, the canary releases for Now CLI are available on three channels: You can either install them through npm, Now Desktop, or by downloading the binaries directly (see how to do that here).

In all three cases, the source is GitHub Releases. In between, our CDN ensures that the files get compressed (GZIP) and are available at maximum download speed in any location in the world. While being streamed to the client, they're decompressed and placed in your local binary directory.

At this point, I'd also like to clear up some confusion about how our npm package is installed: While we do provide a different source for the content of the package (our CDN, as mentioned above), we do not interfere with the decisions npm is making about where to place the binary.

This means that, if you'd like the binaries to be installed in a different location, just configure npm accordingly.

#What Changed

In order to support canary releases, we firstly had to adjust the microservice that contains the CDN's download links to the binary files for all supported platforms of the latest release.

Instead of responding with the latest stable one by default, it now responds with an object containing one property for each channel:

{
  "stable": {...},
  "canary": {...}
}

This brings us right to the next part of what changed: If you have enabled canary updates in Now Desktop, it will start using the canary property (instead of the stable one) to check for Now CLI updates.

When installing it using npm, the flow is a little different: The requests to GitHub Releases will go through the CDN, but won't touch the API mentioned above. That's because the microservice is only meant to cache the latest stable and canary release to prevent Now Desktop or our site (which are both requesting the latest release) to hit GitHub's rate limit.

However, we still had to make adjustments to the way how new npm package updates are published: Because there are now two branches in the GitHub repository, we had to implement two separate config files in order for Travis CI to assign the right dist-tag to each npm release.

Although you can tell Travis CI to deploy a release if the commit was tagged, it's impossible to check for the branch on top of that:

While you can set both conditions (which is needed in our case, because we only want a release to happen if the commit was tagged on a certain branch), the branch one will be ignored if tags is also set:

"on": {
  "branch": "canary",
  "tags": true
}

This is because - in the case of a tagged commit - Travis CI doesn't receive any information about the branch at all.

In turn, we decided to simply create a separate config file for each branch:

On the "master" branch, the package will be tagged with latest on npm. For "canary", however, it will be tagged with canary. To achieve this, we only had to set a property on the deployment:

tag: 'canary'

You can find both config files here:

Now that all of this configuration is in place, we only need to push a tagged commit and create a GitHub Release using release that's marked as a "pre-release". After that, the CI will handle the rest, upload the binaries and publish a new version of the npm package by itself.

From there on, users will be able to install the canary release from npm using the "canary" tag and all Now Desktop instances with the enabled "Canary Updates" preference will receive the CLI update automatically.

#Now Desktop & Hyper

Because we're using Hazel1 to deliver updates to both apps, implementing the canary channel was a matter of just two major changes:

Firstly, we had to deploy two new update servers (since each Hazel instance is designed to handle only a single update channel). For both apps, this was as easy as running two commands.

The first one being the deployment:

now -e NODE_ENV="production" -e PRE="1" zeit/hazel

The most important part about the above command is the PRE environment flag. It tells the update server to only pick up pre-releases.

Then, the second one creating an alias:

now alias <deployment-id> <update-server-alias>

After running these two commands, we were done with the server-side part of the implementation. These are the new Hazel instances providing Now Desktop and Hyper with canary updates (if enabled in the config):

They align very nicely with the existing stable channel instances:

After we've ensured that the update servers are in place, the only thing left was making Now Desktop (PR) and Hyper (original PR, final PR) capable of providing the user with canary updates.

Thanks to Hazel's straightforward approach to updating Electron apps, this was all we had to do. Now both apps have a canary channel!

#General Rules

Along with the adjustments mentioned above, we also ensured that the following rules apply to all projects with canary channels:

  • The repository contains a "master" and a "canary" branch.
  • The "master" branch should only contain stable releases and should not receive any pull requests (except for hot fixes that need to be released to the public immediately).
  • The "canary" branch should be the default branch of the repository. This is where all the work happens and where pull requests land by default. It will be released much more often than "master".
  • Force-pushing and merging without successful tests or reviews is not allowed on the "master" branch.

Once all of the repositories of yours that support canary channels comply with those rules, there should be no more unforeseen consequences generated by publishing code, because it all went through a staging phase.

#Afterword

Thank you for taking the time to read this post!

If you're also publishing software and considering a canary release channel, I highly recommend giving it a try.

Not only does it protect your users from any friction generated by code that's not well enough tested, but it also gives you a separate opt-in channel specifically tailored for playing around with new features and potentially breaking changes.

Have a great day! 😋