Shipping Code. Unopinionated frontend release pipelines delivering complex code, fast.

Andrew Winnicki
Dev Genius
Published in
10 min readJun 8, 2023

--

Writing code is one thing, but getting it safely to production is a totally different challenge, and this part is often forgotten or ignored. Most companies focus on delivering the backend code efficiently, assuming the frontend is not that important (or complex). The result is manual pipelines which require a lot of oversight, effort and remembering steps, often leading to mistakes and problems. And nothing more discouraging to software engineers than processes repeatedly going wrong and causing headaches.

On the other side of the spectrum, we have fully automated CI/CD pipelines which ship code as soon it gets merged to a particular branch. Let’s be honest; only a few companies can implement proper Continous Delivery processes as it’s not easy, and there are gazillion use cases where that’s simply impossible, or at least not worth it. It is considered the Holy Grail of the code delivery.

Let’s find a balance in the world where developers are more and more often responsible for testing (especially in the startup environment). Where well-designed release pipelines with a heavy focus on automated testing and some manual steps seem like the best of both worlds. They give confidence and control over the process to people responsible for delivery, peace of mind, and smooth execution to engineers running them.

Solid fundaments to build a great product and ship it fast. See what works and what doesn’t and improve after time step by step. This is what actual CI is all about — Continous Improvement.

It’s a working example, not a theory or wishlist

Instead of giving you my wishlist and theory on pipelines, I wanted to share a real-world example from Omnevue. This article is not to tell you how you should do things and try to convince you that this approach is the most cutting-edge trend you must follow. This release process works well in a small startup. Still, a similar version was successfully implemented years ago at Yell, where the engineering team peaked at almost 100 developers across 15 teams, and the whole frontend was served from a monorepo.

But, let’s cover the basics first…

What is a good frontend release pipeline?

  • Fully automated deployment steps from the Dev environment up to Production
  • Integrated unit testing, performance, accessibility, end-to-end, and any other type of testing that should be run as part of every release.
  • Self-service. Any engineer should be able to release the code without unnecessary hassle. DevOps role (if available) is limited to maintaining them, not triggering them.
  • Approval checks are in place to ensure things are going through stages as planned, especially when some manual testing is involved.
  • Any “admin” work related to releases, like notifications, changelog updates, and cleanup, should be automated.
  • Flexibility to add & remove actions or block them temporarily.
High-level overview of environments at Omnevue

What do we use to deliver our code to production?

  • ☑️ Buddy.works pipelines (https://buddy.works/)
    I was always a Jenkins type of guy. I like pipelines where I can orchestrate different tasks together without learning new languages or syntax. I call these “unopinionated pipelines” — they do what we need them to do without forcing us to change our working methods. Only BuddyWorks was able to provide a good developer experience. I strongly recommend it if you want a flexible and visual tool to build your release processes. It is obviously the key to our successful code delivery.
  • ☑️ Vite to build our frontend application.
    Our apps are VueJs-based, and Vite made the most sense. Before that, we used Webpack. Technically, it doesn’t matter what you use to bundle up your frontend application as long as it does what you need.
  • ☑️ Node.js
    A bunch of different nodejs scripts help us automate a few things like language pack, router configuration and tests.
  • ☑️ Symantic versioning & changelog
    We completely control our versions and use them to generate changelog files. I wrote about SemVer in detail in another post on Medium, which you can find here:
  • ☑️ SimGit Flow branching strategy
    It might help you to read about it to understand why we are doing certain things. I wrote about this branching strategy in my other Medium article:

Designing a release pipeline

The first step to getting what we want is to understand how our release pipeline should work and what are the key elements. We had to design a high-level step-by-step guide to understand what needed to happen. So we ended up with the following diagram, which outlines the most significant bits. However, it got complicated when we built the actual pipelines, as some of the steps required multiple actions.

We might have the perfect solution in our heads, but it is almost certain we will spot mistakes once they become visual. It’s also easier to share a file with a colleague than thoughts ;)

Parralisiation of tasks

Run as many tasks in parallel as possible to speed up the release process. However, not all jobs can run simultaneously. Any testing is usually your best bet for optimisation: performance, visual, end-to-end, accessibility and simple penetration tests. You can run them all at the same time, and they really shouldn’t affect each other.

Different environments

We decided to split our release into 3 environments (on top of the integration envs where we test new projects and team-developed features). Initially, we would be OK with just two, but now it’s future-proof as the team grows fast.

  • DEV — early testing of the front and backend, especially when database migration scripts must be run. It might get messy, and occasionally we will wipe out the whole database for various reasons. Well, shit happens sometimes, and that’s an excellent way of fixing it :)
  • STAGE — which is a PROD-like environment. Tightly controlled, and it is the closest representation of the Production. We are underutilising this environment, but our pipeline slowly grows as we add new features and automation.
  • PROD — well, our production environment. I don’t think this one needs much of an explanation :)

Notifications

The above diagram does not include notifications as it would become messy and depends on your preference. We decided to use Slack messages sent after all deployments, tests and prompts for approval. That led to quite a few additional actions to do that. Also, we have an extra message when the pipeline fails. Below is an example of all notifications from our build run end to end, and I ensured it failed once to give you all flavours. All these messages are posted to our #tech_deployments channel for anybody to see.

Example Slack notifications from the release pipeline (full cycle)

Implementation

We ended up with the main pipeline, which consist of 4 sub pipes to deliver the code through every stage. I wanted each environment’s deliverables to be separate for easier updates and maintenance, but also to give us better control over what is happening and where.

Subpipelines (2.x) are not meant to be run independently, only triggered by the root release pipeline.

Pipelines overview

2. Frontend Release Pipeline (root)

You can consider it a root pipeline. It is responsible for grabbing the release branch, building the apps, and updating versions. Once this process is done, it triggers all pipelines. Every stage is followed up by an approval process.

Root Frontend Release Pipeline

This reasonably simple root pipeline became complicated once we added all actions required to build the apps. So let me explain a few of the crucial bits from the picture above.

  • Pass Arguments — will ask questions about which apps to build (we have 3 frontend apps, all built from the same monorepo). It also requires a release version (x.xx), as this is a reference to the release branch. Optionally, we can set a maintenance mode message to lock the frontend app on production before deployment.
  • Build the frontend apps — Based on the arguments, it will build all the packages, run unit tests, and update the version of the build.

After that, we copy all the release-related files to different sub-pipelines and kickstart them individually. Deployment to dev, stage and prod is very different; hence, we need separate flows for each step.

2.1. Frontend Release — Deploy to DEV Pipeline

It’s all about getting all three applications (Client, Blueprint & Phorge) to their respective DEV servers. We are also uploading documentation to one of the DEV machines where developers can see all docs generated from the JavaScript code during the build process (we are using JSDoc to do that). After that, some testing and a few Slack notifications to keep us informed on how things are progressing through the pipeline.

The main goal of this step is to allow anybody to test it and, in the end, give us confidence that the portion of work has been completed to the expected standards and that we can capture any issues as soon as possible.

2.2. Frontend Release — Deploy to STAGE Pipeline

Our Stage step is straightforward — get stuff tested in the production-like environment and confirm we are safe to go live. I expect here in the future a few extra steps like:

  • Create user & business data.
  • Run all end-to-end test scenarios.
  • Upload reports.

Note that we are missing deployment of the Blueprint app as it doesn’t exist in the Stage environment at all (that’s on purpose).

2.3. Frontend Release — Deploy to PROD Pipeline

The most crucial pipeline — push everything to production. The goal of this pipe is to take care of the users’ access (maintenance mode if needed), deploy the code, test as much as needed and allow final manual checks if necessary. Due to the heavy testing we perform on DEV and STAGE, release to live is usually a formality.

  • Since we are not doing hot-swap of our servers/environments, we opted for a simple maintenance mode to block users from interacting with the app during deployment.
  • We have extra delays (snooze) to ensure we wait for cache clears on web resources.
  • This pipeline contains many conditional actions, as some might not need to run if we are not deploying all apps or don’t require maintenance mode.
  • To-Do: We still need to integrate smoke tests and improve how the maintenace mode works. Our pipelines are not yet “final”.

2.4. Frontend Release — Post-release Actions Pipeline

At this point, all code has been released & tested. We are happy with the results, and the final release branch can be auto-merged to the main. This process also includes auto-resolving issues in the package.json file that can appear due to how we release hotfixes for older versions of different apps.

The final Slack notification also sends changelog information so everybody can see what has been released to live.

Summary

Done. The code delivered roughly in 15 minutes from start to finish. Although 6 minutes alone is visual testing (there is an opportunity to optimise this step). Usually, it takes longer as we give the whole team (especially non-tech) time to review the changes and raise final concerns before we hit production.

The pipeline works exceptionally well, but most importantly, it does everything for us. We must get everything on a release branch and kick off the process. All admin tasks, notifications, and deployments are handled, and we are guided through the process. Since we have a rule that any engineer can kick off the release process (yes, even juniors and interns), full automation is vital.

Don’t be afraid, be bold. Build what works for you and your team, and stop sacrificing and compromising on out-of-the-box solutions that rarely work as you expect them. It’s not rocket science, and I encourage you to explore options and develop tools and processes that make your life easier and don’t create frustration.

Disclaimer: This article has not been sponsored by Buddy.Works. It’s an excellent tool I recommend exploring.

95% of everything we built can be easily transferred, so we are not stuck with one software forward, which is often the case. Yes, it will take time to set up new pipelines and all the actions, but the most complex steps are NodeJS scripts which can be run anywhere, even on your local machine. That’s what I meant by “unopinionated pipelines”.

Ps. I will write an article explaining how we made our build process, as it’s much more than just Vite bundling.

--

--

Software Engineering Changemaker. Driving digital transformation and sharing experiences and thoughts from my journey. 20 years and counting…