Do your release pipelines look like this? Stop over depending on your deployment tool. You’re making life harder for yourself. Use the Deploy Script Pattern.
Deployment tools such as Azure DevOps and GitHub Actions allow us to quickly create release pipelines for our applications. It’s a fantastic advantage over manual deployment. However, how most people write release pipelines slows development and reduces maintainability.
Over the last ten years, I’ve seen and written many release pipelines. The most common pattern I’ve seen is the heavy use of built-in tasks. Built-in tasks let us provide a few values and then run deployment actions without much effort from developers. On the surface, they are an excellent way to get going fast. However, cracks start to show as you progress through the development lifecycle.
Changes to a release pipeline have a slow feedback loop. For example, a typical change would require the following steps:
Depending on the project size, the time between steps 2 to 6 generally takes five minutes to one hour. Developers are used to near real-time confirmation. An hour is far too long.
If your pipeline requires you to make changes for each environment (Dev, Test, UAT, Prod), you can multiply the time taken to make a simple change by four.
A small change that requires a few loops around the process may not be worth the effort. Bad practices will creep into your project to avoid the long feedback loop. We avoid bug fixes and version updates. No one will want to improve the release pipeline. Even if they did, it would cost too much to make improvements.
I have seen this issue cripple the development of release pipelines. Eventually, becoming the reason for moving to manual deployment.
We need to decrease the feedback loop time to seconds.
At the beginning of every task in a release pipeline, the task’s resources will be downloaded and initialised on the agent. This loading time can be from two seconds to one minute for some tasks. A release pipeline with several tasks can spend significant time in this task-loading state.
The fewer tasks, the better.
However, some deployment tools enable multiple tasks to run in parallel (asynchronously). Running tasks in a release pipeline is useful asynchronously. Tools like Terraform and Pulumi will run their actions asynchronously where they can. If you combine tasks into a single task to save task-loading time, ensure that you make the task perform actions asynchronously. You can write asynchronously in most scripting languages.
It shouldn’t be surprising that using built-in tasks will lock you into the deployment tool you are using. Although this may not seem like an issue when creating the release pipeline, deployment tools have shifted significantly over the last ten years, and changes are still occurring. For instance, GitHub Actions, now a popular deployment tool, was only released to the public in 2019.
Instead, we aim for the least number of tasks in our release pipelines. Only use tasks that set up the agent and then a single task to run a deployment script. Your deployment script should do everything except install software and compile code (compiling should be done by the build pipeline). It can run Terraform/Pulumi, deploy code to web apps, seed databases, deploy batch jobs, and execute smoke tests. Split the different actions into child scripts which a parent deployment script can invoke. Make use of script parameters to pass in environment variables.
This pattern has several benefits:
In the next article, I share an example of implementing the deploy script pattern using Azure DevOps/GitHub, PowerShell, Terraform and ASP.Net. Check it out!
Get the latest blog posts and updates straight to your inbox. Subscribe to my newsletters now!