CICD Trunk-Based Development
Introduction
Trunk-Based Development (TBD) is a source control branching model where developers collaborate on code in a single branch called the "trunk," resisting the urge to create long-lived feature branches. In this approach, the main branch (often called main
or trunk
) is always kept in a releasable state, allowing for continuous integration and continuous delivery/deployment (CI/CD).
This practice stands in contrast to other branching strategies such as Git Flow or Feature Branch Workflow, which rely on multiple long-lived branches. Trunk-Based Development prioritizes integrating code frequently and maintaining a healthy, deployable codebase at all times.
Key Principles of Trunk-Based Development
- Single Source of Truth: The trunk is the single source of truth for the codebase
- Small, Frequent Commits: Developers make small, frequent commits to the trunk
- Feature Toggles: Incomplete features are hidden behind feature toggles
- Continuous Integration: Automated tests run on every commit
- Short-Lived Branches: When branches are used, they last no more than a day or two
Benefits of Trunk-Based Development
- Reduced Merge Conflicts: Frequent integration means smaller changes and fewer conflicts
- Faster Feedback: Developers get immediate feedback on how their changes affect the system
- Improved Collaboration: The team works together on a single branch, encouraging communication
- Simplified CI/CD: Continuous Integration becomes more effective with all changes in one place
- Higher Quality: Issues are caught earlier in the development process
Implementing Trunk-Based Development
1. Setting Up Your Repository
Start with a clean repository structure. Here's how to initialize a new Git repository with trunk-based principles:
# Initialize a new repository
git init my-project
cd my-project
# Create an initial commit on the main branch
echo "# My Project" > README.md
git add README.md
git commit -m "Initial commit"
# Set up branch protection (if using GitHub, GitLab, etc.)
# This would typically be done through the repository settings UI
2. Developer Workflow
In trunk-based development, developers follow this general workflow:
Let's break this down step by step:
a. Pull the latest trunk code
Always start by getting the latest code from the trunk:
git checkout main
git pull origin main
b. Create a local branch (optional)
For slightly larger changes, create a short-lived branch:
git checkout -b feature-x
c. Make small, focused changes
Write your code, keeping changes small and focused on a single task.
d. Test locally
Run tests to ensure your changes work as expected:
npm test # For JavaScript projects
# or
pytest # For Python projects
# etc.
e. Commit and integrate frequently
Commit your changes and push to the trunk (or create a pull request):
git add .
git commit -m "Add feature X"
# If working directly on main:
git push origin main
# If working on a branch:
git push origin feature-x
# Then create a pull request through your Git provider's UI
3. Working with Feature Toggles
Feature toggles (also called feature flags) allow incomplete features to be merged into the trunk without affecting the user experience. Here's a simple example in JavaScript:
// A simple feature toggle implementation
const FEATURES = {
NEW_DASHBOARD: false, // Feature is not ready yet
IMPROVED_SEARCH: true // Feature is ready for users
};
function showDashboard() {
if (FEATURES.NEW_DASHBOARD) {
return renderNewDashboard();
} else {
return renderCurrentDashboard();
}
}
In this example, the new dashboard code can be safely merged into the trunk but won't be visible to users until the toggle is switched to true
.
4. CI/CD Pipeline Configuration
A robust CI/CD pipeline is essential for trunk-based development. Here's an example GitHub Actions workflow:
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up environment
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run linter
run: npm run lint
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: ./deploy.sh # Your deployment script
This workflow runs tests on every pull request and commit to the main branch, and automatically deploys when changes are merged to the main branch.
Real-World Example: E-commerce Application
Let's consider a practical example of implementing trunk-based development for an e-commerce application.
Scenario: Adding a New Payment Method
The team wants to add a new payment method (cryptocurrency) to the checkout process. Here's how they would approach it using trunk-based development:
-
Break Down the Work:
- Update the payment interface to include crypto options
- Implement the crypto payment processor integration
- Add UI elements for the new payment flow
- Update the order confirmation process
-
Create a Feature Toggle:
// In config/features.js
export const FEATURES = {
CRYPTO_PAYMENTS: process.env.ENABLE_CRYPTO_PAYMENTS === 'true'
};
// In payment-methods.js
import { FEATURES } from './config/features';
export function getAvailablePaymentMethods() {
const methods = [
{ id: 'credit-card', name: 'Credit Card' },
{ id: 'paypal', name: 'PayPal' },
];
if (FEATURES.CRYPTO_PAYMENTS) {
methods.push({ id: 'crypto', name: 'Cryptocurrency' });
}
return methods;
}
- Implement in Small Increments:
The team works on small parts of the feature, committing frequently to the trunk:
# Developer 1 - Add payment interface
git checkout main
git pull origin main
# Make changes to the payment interface
git add src/payments/
git commit -m "Add cryptocurrency payment interface"
git push origin main
# Developer 2 - Add UI components
git checkout main
git pull origin main
# Create UI components for crypto payments
git add src/components/payments/
git commit -m "Add cryptocurrency payment UI components"
git push origin main
- Deploy with Feature Toggle Off:
The code is deployed to production with the feature toggle turned off, so users don't see the new payment option yet.
- Testing in Production:
Team members can test the feature in production by enabling the feature toggle for themselves:
# For local development
ENABLE_CRYPTO_PAYMENTS=true npm start
# For specific users in production (using a more sophisticated feature flag system)
- Gradual Rollout:
Once confident in the feature, they gradually roll it out to users:
- Enable for internal users
- Enable for 5% of customers
- Enable for 20% of customers
- Enable for all customers
Challenges and Solutions
Challenge 1: Merge Conflicts
Even with frequent integration, merge conflicts can still occur.
Solution: Pull from the trunk frequently (at least once a day) and keep changes small:
# Start your day by pulling the latest changes
git checkout main
git pull origin main
# If working on a branch, rebase it regularly
git checkout feature-x
git rebase main
Challenge 2: Incomplete Features
Sometimes features take longer than a day to complete.
Solution: Use feature toggles as shown above, or break the work into smaller, independently valuable parts.
Challenge 3: Code Quality
With frequent commits to the trunk, there's a risk of introducing bugs.
Solution: Implement strong automated testing and code review practices:
- Require all tests to pass before merging
- Use code coverage tools to ensure adequate test coverage
- Implement automated code quality checks
- Consider pair programming for complex changes
Comparing Branching Strategies
Here's how Trunk-Based Development compares to other popular branching strategies:
Summary
Trunk-Based Development is a powerful approach to source control that aligns perfectly with CI/CD practices. By keeping the trunk in a releasable state at all times and integrating frequently, teams can reduce integration issues, improve collaboration, and deliver features more quickly.
Key takeaways:
- Commit to the trunk frequently, at least once a day
- Use short-lived branches when needed, but merge them quickly
- Implement feature toggles for incomplete work
- Maintain a robust automated testing suite
- Break large features into smaller, independently valuable parts
Additional Resources
- Trunk Based Development website
- Martin Fowler's article on Feature Toggles
- The book "Continuous Delivery" by Jez Humble and David Farley
Exercises
-
Convert an Existing Project: Take an existing project using a different branching strategy and convert it to use trunk-based development.
-
Implement Feature Toggles: Add a simple feature toggle system to your application.
-
CI Pipeline Setup: Configure a CI pipeline that runs tests on every commit and alerts the team if the trunk build breaks.
-
Simulate Team Development: With a partner, practice making concurrent changes to the same codebase using trunk-based development principles, resolving any conflicts that arise.
-
Measure Improvement: For an existing project, measure the time between code completion and deployment before and after implementing trunk-based development.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)