Un-f*ck your CICD and Branching Strategy

Un-f*ck your CICD and Branching Strategy

Let’s talk about a CI/CD anti-pattern that needs to die: environment branches. You know the ones - dev, qa, stage, prod. It seems logical at first, but over time it leads to pain, confusion, and chaos.

Modern teams should instead rely on short-lived feature branches and artifact-based promotion - a strategy that dramatically improves clarity, agility, and stability in your pipeline.


Why Short-Lived Branches Matter

Short-lived branches reduce complexity. The longer a branch lives, the more it drifts from the mainline, increasing the chance of merge conflicts, integration bugs, and confusion about what’s actually deployed.

By keeping branches short-lived:

Faster Feedback: Changes are merged and tested quickly, reducing integration risk.

Less Merge Hell: Avoid tangled PRs and back-and-forth reviews.

Better CI Hygiene: Every commit triggers a fresh, clean build.

Tighter Control: You know exactly what feature or fix each branch introduces - no bundling or guessing.

Supports Immutable Builds: When every branch produces a build artifact, you have a traceable lineage of what was built, tested, and released.

The Core Problem

When we test something in a lower environment (say QA), then rebuild it before pushing to production, we're introducing risk. A fresh build is not the same build. Even with the same code, dependencies, timestamps, environment variables - anything can introduce variance.

So why do we keep doing this?

The Better Way: Promote Artifacts, Not Branches

Instead of branching for environments, build an immutable, deployable artifact for every commit on every branch. Whether it's a .jar, .tar.gz, or a Docker image, the idea is the same - treat your build as the unit of promotion.

Tag it. Store it. Deploy it.

Why This Works (and What It Solves)

Hotfixes: You can patch main, build a new artifact, and promote just that. Or maybe create a branch from an existing tag, patch it, and promote it.

Parallel Features: You can release features in any order because builds are isolated. If you need 2 features that are in different branches, simply merge them together and build! There's no need to figure out how to get them into some long-lived branch that may have other half ready features integrated.

Rollback: Want to roll back? Just redeploy a previously tested image. No rebuild required.

Auditability: You know exactly what’s running in each environment - and it was tested.

Implementation Tips

1. Tag your images

Use meaningful tags like:

myapp:feature-login

myapp:v1.2.3

2. Release Candidate Flow

Use a simple script or CI job that:

Ensures a clean Git state (no untracked or modified files)

Reads a VERSION file with a -dev suffix during development

Finalizes the version (removes -dev, updates metadata like pom.xml, package.json, etc.)

Tags the commit (e.g. v1.2.3)

Pushes the tag and updated VERSION (e.g. bumps to 1.2.4-dev)

Builds and pushes the final artifact

3. CI Automation

Wrap this in a CI workflow so it's consistent, repeatable, and foolproof.

Final Thoughts

Don’t contort your release process around a branching model that fights against modern CI/CD practices. Design your app to use runtime environment config. Focus on traceable, reproducible builds. And promote artifacts, not code.

Your future self (and your team) will thank you.