DevOps
Designing a Delivery Model That Scales With You: Governance, Traceability, Security, Automation, Testing, and Promotions
Shahbaz Ali Dev.to (EN Zone)
5 views
CI/CD Pipeline Flow — Branching, Release & Deployment Process
The Core Idea: Build once, promote the same immutable artifact.
The simplest mental model: every environment in your SDLC has a corresponding long-lived branch, and code only reaches an environment by merging into that branch.
feature/fix branch → dev → test/sit → staging/uat → prod
Technicalities - merging process flow
Every long-lived branch refer to it's own environment
It's better to start your change/feature/fix/development from any short-lived branch like dev-* named branches created from dev {env} branch, but some times if you want to skip some features not to go to prod like creating a prod-fix/hotfix branch then better to create them from prod branch, and rebase them with dev branch to avoid conflicts as your dev branch might be ahead from prod
Use promotion method like create new short-lived release branches from target branch and cherry-pick or checkout only changed files directly from source/lower env branch to release branch and then merge this release branch with target and once merged delete release branch
The release branches should be created by pipeline better to avoid env conflicts and have a straight forward mechanism, and putting environment specific artifacts out side these repo e.g. vault, platform secrets/configmaps etc same as trunk-based development
Now looking as a full picture of this process will be like:
SHORT-LIVED
feature / fix branches
│
▼
┌─────────────┐
│ DEV │
└──────┬──────┘
│
controlled promotion
│
▼
┌─────────────┐
│ SIT │
└──────┬──────┘
│
controlled promotion
│
▼
┌─────────────┐
│ UAT │
└──────┬──────┘
│
controlled promotion
│
▼
┌─────────────┐
│ PROD │
└─────────────┘
Every promotion is:
┌───────────────────────────────┐
│ Pipeline-created release │
│ branch │
│ ↓ │
│ Selective change propagation │
│ ↓ │
│ Validation / testing │
│ ↓ │
│ Approval │
│ ↓ │
│ Merge to target environment │
│ ↓ │
│ Deploy │
│ ↓ │
│ Delete release branch │
└───────────────────────────────┘
A change is born on a short-lived branch, and it earns its way rightward — never sideways, never backward — through a series of gated merges. Nothing skips a stage. Nothing lands in production without having existed in every environment before it.
This single rule is what makes the rest of the pillars possible.
feature/fix branch → dev (direct merge)
dev → promotes → release-sit → sit
sit → promotes → release-uat → uat
uat → promotes → release-prod → prod
1. Governance — Decide the Rules Before You Need Them
Governance means having explicit, written answers to questions like:
Who is allowed to create a branch, and from where?
Who can merge into dev? Into staging? Into prod?
What has to be true before a merge is allowed (tests passed, approvals given, manifest reviewed)?
What happens to a branch after it's served its purpose?
Write this down as a branch policy, not as tribal knowledge. A simple table works for any org size:
Branch pattern
Push
Merge
Create / Delete
feature/*
developer
developer
developer
dev
—
dev team
—
test / sit
—
dev team
—
staging / uat
—
uat team
—
prod
—
Restricted release group
—
release/*
Pipeline service account only
Pipeline service account only
Pipeline service account only
The last row matters more than it looks: release branches are owned by automation, not by people. A human proposes a release; the pipeline is the only actor that actually creates, merges, and retires the release branch. This removes an entire class of "someone force-pushed to the wrong branch" incidents.
Governance also covers branch hygiene: define when a branch gets deleted. A common pattern is to delete short-lived release branches immediately after merge, but hold onto feature/dev branches until the change has fully landed in production — so you always have a way to trace a production issue back to its origin, even weeks later.
2. Traceability — Every Deployment Should Tell Its Own Story
If someone asks "what's actually running in production right now, and why," you should be able to answer in under a minute, from tooling alone — not from memory or Slack archaeology.
Two mechanisms make this possible:
Environment Branches. Each long-lived branch represents the current state of its corresponding environment. An environment does not necessarily contain every application or feature available in the previous environment. Changes are promoted selectively, so the number of applications or features can decrease as they move through the pipeline.
For example, dev might contain 10 applications or 20 active features, while sit contains only 7 applications or 12 features that have passed testing. uat may contain 5 applications or 8 approved features, and prod may contain 4 applications or 6 features that are actually running in production.
DEV → 10 applications / 20 features
↓ selective promotion
SIT/TEST → 7 applications / 12 features
↓ selective promotion
UAT → 5 applications / 8 features
↓ approved promotion
PROD → 4 applications / 6 features
Auto-generated manifests. Every time a release branch is created, the pipeline should compute exactly what changed relative to the last release and write it into a manifest file that ships with the release branch itself. Reviewers approve the manifest, not just the diff — so the record of "what we intended to ship" and "what we actually shipped" are the same document.
Traceability isn't a nice-to-have for audits — it's what turns incident response from a guessing game into a two-minute lookup.
3. Security & Permissions — Least Privilege, Enforced by the Platform, Not by Trust
The branch table above is really a permissions model, and it should be enforced by your Git host's protected-branch rules or your pipeline's policy engine — not by asking people nicely.
A few principles that generalize across any org:
Wildcard-deny by default. Anything not explicitly listed as an allowed pattern should be unpushable and unmergeable by everyone. Start locked down; open up deliberately, means no one can and should not create any branch rather then having prefix dev-*
Every promotion is reviewed and approved. Dev-facing branches can be wide open to the whole team; but going upward needs approvals from relevant owners or groups.
Machine identities for machine actions. The pipeline should operate under its own service account with its own scoped permissions — and only PR/MR are allowed for release branches created by pipeline.
Tag permissions mirror branch permissions. If only a restricted group can merge to prod, only that same group (or a stricter one) should be able to create the tag that triggers a prod release.
None of this requires enterprise tooling. Most Git platforms support this out of the box with branch protection rules, CODEOWNERS files, merge status check and required-reviewer settings.
4. Automation — Remove Every Manual Step That Doesn't Require Judgment
The rule of thumb: automate everything that's mechanical; keep a human in the loop only for judgment calls.
Mechanical, safe to automate:
Creating the release branch and computing the manifest
Validating that a merge request only touches files it's allowed to touch
Running the test suite and blocking merges on failure
Publishing build artifacts or catalog entries once a merge lands
Deleting spent release branches after a successful merge
Judgment, keep human:
Approving the manifest before a release proceeds
Approving the merge into staging/production
Deciding whether a hotfix skips normal branch-from-prod rules
A useful trigger pattern for teams who want releases on-demand rather than continuous: let a specific git event to trigger pipeline. This gives teams control over when a release cuts, without needing a separate manual pipeline run button, and it keeps the trigger itself inside version control history — one more thing that's traceable for free.
5. Testing — Gate Every Promotion, Not Just the Final One
A pipeline that only tests before production is a pipeline that finds problems too late. Instead, treat every promotion as a gate:
Merge-time checks on every branch merge: linting, unit tests, mapping/schema validation, security scans.
Environment-specific validation after deploying to test/staging: integration tests against real dependencies, contract tests, smoke tests.
Manifest review as a lightweight human test: does the list of what's changing match what the team expects to ship?
The earlier a check runs, the cheaper the failure. Pushing all validation to the final gate just moves the cost of a bug from "an hour to fix" to "a rollback in production."
6. Promotion and Rollback — One Flow, Repeated at Every Stage
The payoff of getting the first five pillars right is that promotion becomes boring — in the best sense. The same shape repeats at every environment boundary:
Merge the change into the source environment's branch.
Trigger the release branch and manifest for the next environment.
Review the manifest and the diff.
Approve the merge.
Publish, and clean up branches that have served their purpose.
Whether you're promoting dev → test, test → staging, or staging → prod, it's the same five steps with different names and a tighter permission group at each hop. New team members only need to learn the pattern once.
Rollback follows the same governed flow. If a release introduces an issue, identify the affected release using its tag and manifest, select the last known-good release, create a rollback release from that version, run the required validation, and promote it through the same approval process.
Why This Scales
This design doesn't assume a specific number of environments, a specific tech stack, or a specific team size:
Small teams can run this with just three branches (dev, staging, prod) and looser permissions — the pattern still holds.
Large orgs can add as many intermediate environments as needed (sit, uat, perf, pre-prod) without changing the underlying model — each is just another link in the same chain.
Any application type — a monolith, a set of microservices, an API gateway configuration, an infrastructure-as-code repo — fits the same branch-and-gate structure, because the model governs how change moves, not what the change contains.
The tools you plug in (GitHub, GitLab, Jenkins, ArgoCD, whatever) are replaceable. The design — governed branches, automated and traceable promotion, tested gates, least-privilege access — is what actually survives a tool migration, a team reorg, or a 10x growth in headcount.
If you're setting this up for the first time: start with the branch table and the wildcard-deny rule. Everything else — manifests, tags, automation triggers — can be layered on once that foundation is enforced.
Read original: https://dev.to/shahbazalishahid/designing-a-delivery-model-that-scales-with-you-governance-traceability-security-automation-mhc
← Previous
We built a Splitwise alternative where the link is the account. Here is what that forced us to design
Next →
Apple Watch’s new AI features are normalizing the idea that technology is always listening
Related
Comments0
No comments yet — be the first