From batch releases to feature releases: rethinking our CI/CD strategy
A look at our planned shift from batch releases to feature-based CI/CD to improve delivery speed, reduce risk, and support continuous delivery.
At Checkmyfile , we're currently operating a Gitflow-styled CI/CD process, where changes are promoted through environments and new features are typically released at the end of our 3-week sprint cycle.
This has served us well as we transitioned from on-prem to cloud, introducing full CI/CD workflows just a few years ago. But as our confidence and processes have matured, we're starting to outgrow it.
We're now looking to reduce risk and increase our release cadence.
At its core, this change is simple:
We’re moving from promoting branches through environments to promoting individual features to production
This shift allows us to release work independently, rather than in batches.
Current model
Our current model can best be described as:
A Gitflow-style environment promotion model
We have 4 infrastructure environments, with corresponding branches in our repositories:
develop- a moving environment. Engineers branch from here and merge new features back into it. On PR completion, work is automatically deployed to thedevelopenvironment.qa- the QA team mergesdevelopintoQAand tests on this branch. Deployments are gated and require approval in our pipelines. The intention is to ensure the environment doesn’t move under QA’s feet.prelive- our staging environment, using live data but limited to internal users. It is also gated and used for testing live integrations and UAT (which is a topic for another post).main- the production branch. Naturally, this has stricter approvals and deployment controls.
To summarise:
- Work is merged into a shared branch -
develop - That branch is promoted through environments
- Releases (outside of bug fixes) are made as batches of changes
Problems with the current model
There are a number of challenges with this approach, but these are the most impactful:
Features block each other
Multiple features are bundled together. If one feature fails testing, it prevents other features from being promoted through the environments.
If we discover an issue later in the sprint - for example, a failure in prelive just before release - it can disrupt the entire release process and lead to last-minute scrambling.
Ultimately, completed work cannot be released independently.
A typical scenario for us:
- Feature A passes QA
- Feature B fails QA
- Neither can be released without additional work or rollback
Large batch releases
Releasing many changes at once increases the risk of defects and makes it harder to isolate issues when something goes wrong.
Slow time to production
Features often sit waiting for release cycles. We can have work completed and tested within the first few days of a sprint, but it may still take weeks to reach customers.
This creates a significant gap between “completed” and “released”.
New model: Feature-Based Releases
With our new model, we’re introducing a key principle:
Promoting individual features to production
Instead of releasing everything in develop at the end of a sprint, we want the flexibility to release features as soon as they are tested and approved.
Our goals are:
- Release features as soon as they’re ready
- Reduce batch size and release risk
- Decouple features so they no longer block each other
For a direct comparison:
- Branch promotion → Feature promotion
- Fixed release cycle (3 weeks) → Release when ready
- Higher risk (more changes) → Lower risk (fewer changes)
New branching strategy and flow
We’ll use two primary branches:
main- production. This branch is always considered deployabledevelop- integration. Changes here are deployed to thedevelopandQAenvironments
Development flow
- Create a feature branch from
main - Develop the feature
- Merge into
developafter peer review - Automatically deploy to
developandQA - QA performs testing
- Once approved, merge the same feature branch into
main - Automatically deploy to
prelive - After approval, deploy to
production
The key difference is that develop is no longer treated as a release candidate.
Instead of promoting develop through environments, we selectively promote individual features to production via main.
We’re also removing gated deployments in non-production environments, allowing us to move toward continuous deployment with greater confidence.
Mindset changes
Moving from batch releases to continuous delivery requires a shift in how we work.
Keep changes small and think incrementally
We want short-lived branches and smaller PRs.
Long-running branches drift away from production and increase risk. Breaking work into smaller increments keeps changes easier to review, test, and release.
Increased use of feature flags
We already have a flexible feature flag system, supporting:
- user-level targeting
- exclusions
- percentage rollouts
We’re confident in our use of feature flags - they now need to become standard practice.
Respecting contracts
A contract is the interface between systems:
- API request/response structures
- Endpoint behaviour
- Data formats
A contract defines what one part of the system expects from another
This is what enables this model.
Without backwards compatibility, independent releases become risky or tightly coupled.
In batch releases, coordination is less critical because everything is deployed together. In this new model, we must design for compatibility.
The backend cannot remove fields still used by the frontend. Likewise, the frontend must not assume new behaviour is available until it is.
Before making any contract change, we ask:
1. Will the current frontend continue to work after this backend change?
2. Will the current backend continue to work with this frontend change?
If not, the change must be phased or redesigned.
Increased ownership
A quote that stuck with me from an article I recently read on Increment , was:
you don’t “own” the code you write, unless you run and maintain it, too
We want engineers to own features end-to-end, from PR to production, with Engineering leadership acting as approvers rather than gatekeepers.
Migration approach
We won’t switch everything at once.
Instead, we’ll:
- Start with a pilot service
- Validate the approach
- Roll out repository by repository
In-flight work will be handled pragmatically:
- Work close to release may complete under the current model
- New work will adopt the new model
This reduces risk and gives us room to learn.
Our future direction: trunk-based
Our long-term goal is to move toward:
- Changes merged directly into
main - Continuous releases
- Strong automated testing
- QA supported by automation
To get there, we need improvements in:
- end-to-end testing
- CI reliability
- backwards compatibility
- feature flag usage
- incremental delivery
Ephemeral environments
We’re also exploring ephemeral environments - spinning up temporary environments per PR.
This would allow isolated testing and reduce reliance on shared environments.
We’re not quite there yet, at an IAC level, but it’s a natural next step.
Pure trunk-based development can feel unrealistic in practice. Guardrails like ephemeral environments and some manual QA help maintain confidence without over-relying on engineers writing automations to mark their own homework.
Closing thoughts
This isn’t about adopting a trendy CI/CD model. It’s about reducing risk, increasing delivery speed, and giving teams more control over what reaches production.
A feature-based release model should allow us to deliver value more frequently, with less risk.
We’ll learn a lot during the rollout - what works, what doesn’t, and where the real friction is. I’ll share a follow-up once we’ve put this into practice!