In this post, we’ll dive into Trunk-Based Development and Gitflow, two popular branching strategies in software development. Through practical examples, we’ll set up GitLab CI/CD pipelines and deploy a sample application on a Kubernetes cluster to showcase how each strategy can be effectively implemented.
Prerequisites
- Basic Knowledge: Familiarity with Git, GitLab, Docker, Python, and Kubernetes.
- Environment Setup:
This demonstration uses GitLab as the version control system and CI/CD platform. Follow this tutorial to install and configure GitLab locally.
Kubernetes is used to host the Python application. Refer to this guide for setting up CI/CD pipelines and deploy python app to a local Kubernetes cluster using KIND (Kubernetes IN Docker).
The application will be deployed to multiple environments:
sandbox
,dev
,qa
,stg
,prod
To represent each environment, we will create corresponding Kubernetes namespaces.1 2 3 4 5
kubectl create ns sandbox kubectl create ns develop kubectl create ns qa kubectl create ns stg kubectl create ns prod
Trunk based development
Trunk-Based Development is a streamlined version control strategy where all developers work on a single branch, known as the trunk
and frequently integrate their changes. It minimizes long-lived branches, reducing merge conflicts and promoting continuous integration. This approach enables faster delivery cycles, making it ideal for teams practicing DevOps and Continuous Delivery.
Trunk ci/cd pipeline
Assuming you already have a hello-world
Python application repository set up in GitLab and KIND Kubernetes cluster is up & running as outlined in the prerequisites section. Lets setup the ci/cd pipeline required for the Trunk based development workflow.
Navigate to the Python app folder, add the following .gitlab-ci.yml
file at the root level, and commit the changes directly to the trunk branch.
|
|
Trunk workflow
The developer begins working on the user story/ticket, creates a feature branch from the main branch and uses the naming pattern feature/*.
1 2 3
git checkout main git pull origin main git checkout -b feature/feature-1 main
The developer starts developing the feature. Modify the
app.py
file to include the new feature.1 2 3
@app.route('/greet/<name>') def greet_user(name): return f'Hello, {name}!'
The developer commits and pushes the changes to feature branch, the pipeline builds, tests, pushes the image to registry and deploys the application to the sandbox environment.
1 2 3
git add . git commit -m "Add user greeting feature" git push origin feature/add-user-greeting
Once the feature is complete, the developer push the changes to feature branch and create a merge request (MR) from the feature branch into the main branch by using a squash merge.
When the MR is approved and merged into main branch, the pipeline runs, builds, tests, pushes the image to registry. Also the feature branch is deleted once the changes are merged into main branch.
An approver manually triggers the deployment to the
dev
environment, where the app is deployed using the image created in the previous pipeline stage, with the image tag set toCI_COMMIT_SHORT_SHA
.An approver manually triggers the deployment to the
qa
environment, deploying the app with the same image and tag as in the dev environment.An approver manually triggers the deployment to the
stg
environment, deploying the app with the same image and tag as in the dev environment.An approver manually triggers the deployment to the
prod
environment, deploying the app with the same image and tag as in the dev environment.
Gitflow branching strategy
Gitflow uses two main branches, main
and develop
, along with feature
, release
, and hotfix
branches for specific purposes.
main
ormaster
: The primary branch where stable and production-ready code lives.develop
: The branch where ongoing development happens. It is the integration branch for features, meaning that all new features are first merged here before being released.feature/*
: Feature branches created from develop for new features.release/*
: Release branches created from develop when a version is ready to be built, tested and deployed to production.hotfix/*
: Hotfix branches created from main branch to address critical issues in production.
Gitflow ci/cd pipeline
Navigate to the Python app folder and add the following .gitlab-ci.yml
file to the root of the folder. Then, push the changes to the main
branch.
|
|
Gitflow workflow
Gitflow relies on two long-running branches: main and develop. Since the main branch already exists, we need to create the develop branch
|
|
This establishes the develop branch as the primary branch for ongoing development work, separate from the stable main branch.
Let’s walk through how the Gitflow workflow would apply to developing and managing your simple Python application hello-world
The developer wants to add a new feature to python app, creates a feature branch from the develop branch and uses the naming pattern feature/*.
1
git checkout -b feature/add-user-greeting develop
The developer starts developing the feature. Modify the
app.py
file to include the new feature.1 2 3
@app.route('/farewell/<name>') def farewell_user(name): return f'Goodbye, {name}! Have a great day!'
The developer commits and pushes the changes to feature branch, the pipeline builds, tests, and deploys the application to the sandbox environment.
1 2 3
git add . git commit -m "Add user greeting feature" git push origin feature/add-user-greeting
Once the feature is complete, the developer push the changes to feature branch and creates a merge request from the feature branch into the develop branch by using a squash merge.
When the MR is approved and merged into develop branch, the pipeline builds, tests, pushes the image to registry and deploys the application to the dev environment.
(Optional) A developer integrates additional feature branches into the develop branch prior to continuing with release activities.
When the developer is ready to release the features in the develop branch, the developer creates a release branch using the naming pattern release/* from the develop branch and push the release branch.
1 2
git checkout -b release/v1.0.0 develop git push origin release/v1.0.0
The release pipeline is triggered and automatically builds, tests, pushes the image to registry(artifact) for reuse across other environments.
An approver manually triggers the deployment to the
qa
environment, where the app is deployed using the image created in the previous pipeline stage, with the image tag set toCI_COMMIT_SHORT_SHA
.An approver manually triggers the deployment to the
stg
environment, deploying the app with the same image and tag as in the qa environment.An approver manually triggers the deployment to the
prod
environment, deploying the app with the same image and tag as in the qa environment.The developer creates a merge request from the release branch to the main branch. Once the MR is approved, it is merged into the main branch using a fast-forward merge, avoiding squash merges.
After release branch is merged into main, the developer commit the tag on the main branch.
|
|
- The developer creates a merge request from the release branch to the develop branch. Once the MR is approved, it is merged into the develop branch using a fast-forward merge, avoiding squash merges. The release branch can be deleted after the code is merged successfully.
Hotfix process
A hotfix in Gitflow is a dedicated branch created to address critical issues or bugs found in the production environment after the release branch has been merged into the main branch and the release branch is deleted. It allows developers to quickly patch the issue without disrupting ongoing development work in the develop branch or other feature branches.
The developer creates a hotfix branch from main branch with the naming pattern hotfix/*
1 2 3
git checkout main git pull origin main git checkout -b hotfix/v1.0.1 main
The developer fixes the issue, push the changes to hotfix branch, The hotfix pipeline is triggered automatically which builds, test and deploys the application to sandbox environment(to test if the fix is working properly).
1 2 3
git add . git commit -m "fix issue" git push origin hotfix/v1.0.1
The developer creates a release branch from the main branch with naming pattern release/*
1 2 3 4 5
git checkout main git pull origin main git checkout -b release/v1.0.1 main git commit -m "[skip ci] create release/v1.0.1 branch" # [skip ci] to avoid trigger release pipeline before MR. git push origin release/v1.0.1
The developer creates a merge request from the hotfix branch into the release/v1.0.1 branch by using a squash merge.
When the MR is approved and merged into release branch, the pipeline builds, tests, pushes the image to registry.
An approver manually triggers the deployment to the
qa
environment, where the app is deployed using the image created in the previous pipeline stage, with the image tag set toCI_COMMIT_SHORT_SHA
.An approver manually triggers the deployment to the
stg
environment, deploying the app with the same image and tag as in the qa environment.An approver manually triggers the deployment to the
prod
environment, deploying the app with the same image and tag as in the qa environment.The developer creates a merge request from the release branch to the main branch. Once the MR is approved, it is merged into the main branch using a fast-forward merge, avoiding squash merges.
After release branch is merged into main, the developer commit the tag on the main branch.
1 2 3 4
git checkout main git pull origin main git tag -a v1.0.1 -m "Release version 1.0.1" git push origin main v1.0.1
The developer creates a merge request from the release branch to the develop branch. Once the MR is approved, it is merged into the develop branch using a fast-forward merge, avoiding squash merges.
Bugfix process
A bugfix is performed during the release process before the code is merged into the main branch, This is a common scenario in Gitflow when an issue is discovered in the release branch during testing or validation.
The developer creates a bugfix branch from the current release/v1.0.0 branch and uses the naming pattern bugfix/*.
1 2 3
git checkout release/v1.0.0 git pull origin release/v1.0.0 git checkout -b bugfix/bug-1 release/v1.0.0
The developer fixes the issue, push the changes to bugfix branch, The bugfix pipeline is triggered automatically which builds, test and deploys the application to sandbox environment(to test if the fix is working properly).
The developer creates a merge request from the bugfix branch into the release/v1.0.0 branch by using a squash merge.
When the MR is approved and merged into release branch, the pipeline builds, tests, pushes the image to registry.
An approver manually triggers the deployment to the
qa
environment, where the app is deployed using the image created in the previous pipeline stage, with the image tag set toCI_COMMIT_SHORT_SHA
.An approver manually triggers the deployment to the
stg
environment, deploying the app with the same image and tag as in the qa environment.An approver manually triggers the deployment to the
prod
environment, deploying the app with the same image and tag as in the qa environment.The developer creates a merge request from the release branch to the main branch. Once the MR is approved, it is merged into the main branch using a fast-forward merge, avoiding squash merges.
After release branch is merged into main, the developer commit the tag on the main branch.
1 2 3 4
git checkout main git pull origin main git tag -a v1.0.0 -m "Release version 1.0.0" git push origin main v1.0.0
The developer creates a merge request from the release branch to the develop branch. Once the MR is approved, it is merged into the develop branch using a fast-forward merge, avoiding squash merges.
Trunk vs Gitflow
Aspect | Trunk-Based Development | Gitflow |
---|---|---|
Workflow Structure | Relies on a single main branch with short-lived feature branches or direct commits. | Uses long-lived branches (develop , main ) with release and feature branches for parallel development. |
Release Cadence | Ideal for frequent, continuous releases. | Suited for planned, periodic releases. |
Complexity | Simpler workflow, but requires discipline to keep main stable. | More complex due to multiple branches and merges, suitable for larger teams. |
Pros | - Faster release cycles. - Easier to automate with CI/CD. - Minimal branch management overhead. | - Clear structure for managing large projects. - Safer for parallel work and strict release cycles. |
Important notes
- Restrict developers from directly pushing to the main branch to maintain its integrity. Use merge requests and appropriate approvals to update it.
- In Trunk based development, when integrating incomplete features into the main branch during development, ensure they remain hidden or non-functional in production by using feature flags.
- In Gitflow, production deployments can be made from either the release branch or the main branch. I recommend deploying from the release branch and merging into the main branch only after verifying that everything works fine in the production environment. However, you can adjust the pipeline if you prefer deploying from the main branch.
- In Gitflow, always ensure a fast-forward merge from the release branch to the main branch to avoid creating an extra merge commit and maintain a clean, linear history in the main branch.
- Image promotion:
- For demonstration purposes, I used a single GitLab registry for all environments. In a real-world setup, you might use a separate image registry for each environment. You can modify the pipeline to support image promotion by adding push jobs for each environment to push images to the appropriate registry before app deployment.
- If you’re using AWS Elastic Container Registry (ECR), consider enabling cross-account and cross-region image replication to replicate images automatically across environments instead of creating multiple push/promote jobs in the pipeline.
Happy deploying! 🚀