Understading the connection between branching models and delivery pipeline
After a while I’ve published the “The best branching model to work with Git”, I noticed that even if the branch strategy is well defined, its connection to the delivery pipeline may still be unclear.
Some of the questions I’ve heard are:
- Should I have one branch for each environment?
- From which branch should the deployments come from?
- Should I integrate my ready-to-be-released code with master before or after it’s published on production?
In this post, I’ll review the concepts of Continuous Integration, Continuous Delivery and Continuous Deployment, reinforce some branching principles and connect these concepts with the most suitable approach of delivering model.
Firsts things first: How to properly generate a deployable package
Before talking more about deployment models, it’s important to understand how to generate a portable deployment package that can be released on different environments. Let’s make a quick review of Continuous Integration and Continuous Delivery concepts.
Continuous Integration (CI) is a DevOps practice that provides feedback about the integrity of the code being pushed to the source repository. Each change on the repository triggers a build, and a successful build generates and stores an artifact — a package ready to be deployed on an environment, like a compressed folder with binaries or a docker image, for instance.
A CI process execution is also named a build. Usually, the artifact is tagged with the build identifier, enabling tracing — with integrated solutions like VSTS or Jira, an artifact can be tracked down to its build, which can be tracked to the git push identifier that triggered it, and the git push can be tracked down to its related features or user stories.
Below there’s an example of a CI process using VSTS as an integrated solution:

For an artifact to be deployed on different environments, a requirement must be fulfilled: the application must be able to access the environment configuration without needing to be recompiled. Preferably, its configuration should come from an external source. If the configuration changes, the running application should get the updated values with a restart or another refresh mechanism.
This is well described in the Config principle or the 12-Factor App methodology. The only thing that should change depending on the environment is the configuration — it can be a configuration file, usually JSON, YAML or XML, a set of environment variables, a configuration service or database — whether strategy you use to hold the configuration data, the application has to be ready to access it without needing to be recompiled.

With an artifact properly generated, and the configuration setup of each environment the application should be deployed on its way through production, you are able to create a Continuous Delivery pipeline.
Continuous Delivery is the DevOps discipline that involves automating the process of deploying an artifact on every stage it’s needed. An approval flow can be defined at each stage, along with pre and post conditions, like validating the prerequisites, connections, and dependencies, and post-deployment validations, to ensure the version is stable on the environment. The delivery pipeline is commonly triggered by a Continuous Integration (CI) process.

Each stage is designed to a specific validation purpose, with a specific target audience. How many stages before production may vary depending on the case. If it’s a corporative solution, it may have more stages, if it’s a lean product, it may have less. But the reliability of the validation tests depends on the artifact’s integrity: if it has suffered any changes, there’s no guarantee that it would still work as expected when deployed to the next environment.
How branching model and deployment models correlate
In my article “The best branching model to work with Git” I explained how can a proper branching strategy be helpful for you and your team to organize and integrate your application’s code. But the DevOps practices I described on the previous section (CI and CD) are intrinsically related to the branching model — a CI process is triggered after a change on a specific branch, and a CD pipeline usually is integrated to a CI process. So I’ll step into the most common doubt’s that may occur when integrating the branching model with the deploying model.
Should I have one branch for each environment?
I’ve seen a lot of companies that designed a branch for each environment stage. So if there are three stages, DEV →BETA →PROD, there would be three branches too.
If we would automate the delivery pipeline for this model, it wouldn’t be sequential as the previous picture. It would look something like this:

In this model, the new features are integrated on DEV branch, and deployed on DEV environment for validation and feedback. When they are ready to be delivered, DEV branch is merged into BETA branch, which will generate a deploy to BETA environment. After it’s approved, BETA branch will be merged with PROD (master), generating a deploy on production. The code is being promoted, not the artifact. Notice that at each stage, a new artifact version is generated and deployed.
The picture also describes what happens when the environment configuration is inside the application’s source control. Unless there are as many configuration files as environment stages, there’s no way the application be portable. Even so, if any change of configuration happens, it would have to be committed on the source repository, and it would generate a new build, with a new artifact’s version and a new deploy.
If you have a branch for each environment, you won’t be able to create a single sequential delivery pipeline. It’s a problem because at each validation stage, you have to generate a new artifact, so your team is validating different artifacts (in this example, #v1, #v2 and #v3). The whole point of having validation stages is broken, since the artifact is not the same, and what you have approved on the previous environment may not work on the next.
You should promote the artifact, not the source code. So the answer is no, you shouldn’t have a branch for each environment. A specific branch should be integrated with the CI process, and its artifact should be promoted through each stage until production. The Continuous Delivery pipeline should reflect the environment stages, not the branch model. This approach guarantees you the integrity of the artifact and enables you to isolate environment issues, making troubleshooting easier: if a bug is found only in a specific environment, it’s probably related to the environment itself — a misconfiguration, or a problem with one of the connected services or dependencies.
Which leads to the next question:
From which branch should the deployments come from?
Suppose that you have features ready to be validated and delivered. If the CI/CD is triggered by the master branch, each feature branch should be merged on master to be deployed. Now, suppose that your team merged the last feature branches on master, and a bug is found on production before the validation process is finished. You cannot extract a branch from master to fix the bug, because it no longer reflects what is available on production.
You would have to take a more complex approach, like discarding the latest changes on the master branch to rollback the source to the production’s version — otherwise, the team would end up bringing unvalidated work along with the bug fix.
After the application is deployed on production for the first time, it’s unsafe to merge feature branches on master if they are not ready to be released to final users. If a bug is found, there will be no active branch with the same version code as production to correct the bug and quickly provide a fix.
Master cannot be changed until the code is approved to be released on production, so the CI/CD process should be triggered from any other branches — feature, integration, release or bugfix branches. Which one? Depends on the deployment model.
Continuous Delivery vs Continuous Deployment
On Continuous Delivery, all incoming artifacts on the pipeline are potential releases on production, but not necessarily they will be deployed on all stages. Usually, a group of features is released periodically.
That’s the reality for most of the digital solutions. A group of features, bugfixes and improvements are developed on a time interval, validated and released to the final users. If there’s only one release being worked per time, an integration branch between the master branch and feature branches should be enough. If while a release is being validated, another one with different features can be started, then each release should have its own integration branch.

In the picture above, each feature merged with integration branch generates a new build. The artifacts produced on #build1, build2 and #build3 were deployed at least on the first environment stage, but only #build3 was approved to be deployed on production.
The branch responsible for integrating the new features and grouping it into a new version of the product should trigger the production pipeline, whichever name it has — develop, integration, or release.
Another type of branch that should trigger a production pipeline is the bugfix/hotfix type. When a critical bug is found on production, a fix has to be provided fast. So usually there’s a shorter CD pipeline for it, with fewer environment stages until production.
What about feature branches? When possible, they should have its own pipeline, with an exclusive validation environment. It’s interesting to have isolated feedback from the features being developed, without interfering on any other environment in use. When they are accepted, they can be merged on integration branch and start the production pipeline.
There’s a different approach, when each feature is delivered to production after they’re finished. We’re talking about Continuous Deployment.
Continuous Deployment is an extension of Continuous Delivery, with a special condition: every finished feature is deployed automatically on production.
In this scenario, integration branch is no longer necessary. Feature branches will be connected to a production pipeline.
A good example is the GitHub Flow model (very different from the gitflow model). New features are delivered on production as soon as they’re finished. After a while, they’re integrated on master, so if an unstable version is deployed, it can be discarded without corrupting the master branch. It’s a very Agile and Lean approach — it enables to receive (very) quick feedback for what is being developed. It can work well if the users are collaborative — like Github itself. If a bug is found, it probably would be reported by its own users.
It may have answered the next question:
Should I integrate my ready-to-be-released code with master before or after it’s published on production?
If you merge your feature, bugfix or integration branch on master before it’s published on production, you’ll have at least one serious risk: If the deployment fails and the product’s version has to be rolled back, you’ll have to discard the last merged changes on master too. Master branch should always reflect production. It would be simpler to wait until the post deployment tests are finished before merging into master.
Conclusion
Each business model asks for a suitable branching and deployment model. But there are some recommendations that are helpful for maintaining a live product, and being able to add improvements and corrections in a reliable way. The highlights are
- Promoting the artifact, not the code
- Keeping master branch compatible with production environment
- Having a delivery pipeline integrated to the release branches, with one or more stages of validation environments depending on the nature of your business
- Having a separated bugfix pipeline to quickly provide critical fixes
- Having a separated feature pipeline whenever needed to provide isolated feedback of what is being developed