
CI/CD Is Not Just a Pipeline: Building Delivery Systems That Scale
I have a recurring conversation with engineering teams that goes something like this:
"We have CI/CD."
"Tell me about it."
"We have a pipeline that runs tests and deploys to production."
"How confident are you in every deployment?"
"...it depends."
That "it depends" usually reveals a significant gap between having a pipeline and having a delivery system.
A pipeline is a sequence of automated steps. A delivery system is the engineering practice, tooling, and organizational discipline that ensures every change can be shipped safely, quickly, and with confidence.
Most teams have the pipeline. Very few have the system.
The pipeline is the easy part
Setting up a CI/CD pipeline is straightforward. Every major platform — GitHub Actions, GitLab CI, Azure DevOps, CircleCI — provides templates that get you from zero to automated deployment in an afternoon.
Build. Test. Deploy. Done.
Except it is not done, because the pipeline answers the question "how does code get to production?" without answering the harder questions:
- How do we know this deployment is safe?
- How do we roll back if something goes wrong?
- How do we deploy to a subset of users before going global?
- How do we coordinate deployments across dependent services?
- How do we handle database migrations that cannot be rolled back?
- How do we prevent a broken change from reaching production at 4pm on a Friday?
These are delivery system questions, and they require engineering beyond YAML configuration.
What a delivery system actually includes
A mature delivery system has several components that go beyond the pipeline itself.
Release engineering
Release engineering is the practice of managing how software is packaged, versioned, and promoted through environments.
In mature delivery systems, a release is an artifact: a container image, a versioned bundle, or a deployment manifest that has been tested and is ready for production. The release is built once and promoted through environments, not rebuilt for each environment.
I help teams implement:
- immutable artifacts — build once, deploy everywhere
- semantic versioning — clear communication about the nature of changes
- release channels — stable, canary, beta tracks for different audiences
- promotion gates — automated and manual checks that must pass before a release advances to the next environment
Progressive delivery
Deploying everything to 100% of production at once is the most common and most dangerous pattern.
Progressive delivery means shipping changes to a controlled subset of users or infrastructure before expanding to the full population. This includes:
- canary deployments — deploy to a small percentage of traffic and monitor for errors
- blue-green deployments — maintain two identical environments and switch traffic between them
- feature flags — decouple deployment from release, enabling code to be deployed but not activated
- ring-based rollouts — deploy to internal users first, then early adopters, then general availability
The key insight is that deployment and release are separate concepts. Deployment puts code on servers. Release exposes it to users. Separating these gives you a critical safety window between shipping and exposing.
Deployment safety
Every deployment should have a safety net. The elements I recommend:
- automated rollback — if health checks fail after deployment, the system rolls back automatically
- deployment windows — restrict deployments during high-risk periods (Friday afternoon, peak traffic hours)
- change freeze enforcement — automated blocks on deployment during incident response or holiday periods
- deployment rate limiting — prevent too many changes from deploying simultaneously
- required approvals — for critical services, require a second pair of eyes before production deployment
Database migration safety
Database migrations are the most dangerous part of most deployments. Unlike code, database changes are often irreversible.
I recommend:
- expand-and-contract migrations — add new columns/tables first, migrate data, then remove old ones. Never make destructive changes in a single step.
- migration testing in staging — with production-sized data, not empty databases
- migration rollback plans — for every migration, document how to undo it
- separate migration and code deployment — migrate the database before deploying the code that depends on it
Dependency coordination
In microservices architectures, services depend on each other. A breaking change in Service A might require a coordinated deployment with Service B.
I help teams manage this through:
- API versioning — always maintain backward compatibility during transitions
- consumer-driven contract testing — verify that changes in a provider do not break its consumers
- deployment ordering — automated or manual control over which services deploy first
- compatibility windows — the old and new versions of a service must coexist briefly during rollout
The organizational dimension
Delivery systems do not fail only for technical reasons. They fail for organizational reasons.
Common organizational failure patterns:
- No shared ownership of the delivery system. The pipeline is maintained by the team that set it up. When they move on, it becomes legacy.
- Inconsistent practices across teams. Each team has its own deployment process, making it impossible to reason about organizational deployment velocity or risk.
- No investment in delivery tooling. Teams spend time on features but treat deployment as a solved problem.
- Deployment heroics. A few senior engineers know how to deploy safely. Everyone else follows documented steps that are often outdated.
The fix is to treat delivery as a platform capability:
- a shared, maintained, and evolving set of tools and practices
- supported by a platform or DevOps team
- opinionated enough to be useful, flexible enough to accommodate different service types
- documented, tested, and continuously improved
Metrics that matter
You cannot improve what you do not measure. For delivery systems, I recommend tracking:
- Deployment frequency — How often are teams deploying? Higher frequency (with safety) is better.
- Lead time for changes — How long from commit to production? Shorter is better.
- Change failure rate — What percentage of deployments cause an incident? Lower is better.
- Mean time to recovery (MTTR) — When a deployment fails, how quickly is it resolved? Faster is better.
These are the DORA metrics, and they are well-validated as predictors of engineering team performance. But measuring them requires instrumented delivery pipelines that track deployments, detect failures, and correlate with incidents.
Practical recommendations
If your delivery system is currently just a pipeline, here is where I suggest starting:
-
Separate build and deploy. Build immutable artifacts once, promote them through environments.
-
Add deployment health checks. After every deployment, automatically verify that the service is healthy. Automate rollback if it is not.
-
Implement a canary strategy for critical services. Even a simple 5%/95% canary split with automated monitoring catches most deployment-related issues.
-
Track the DORA metrics. Instrument your pipeline to measure deployment frequency, lead time, change failure rate, and MTTR.
-
Standardize across teams. Create a shared deployment workflow that handles the common cases, with escape hatches for special requirements.
-
Invest in database migration safety. Adopt expand-and-contract migrations and test them against production-sized databases.
-
Document your rollback procedures. Every service should have a tested, documented way to roll back a deployment.
Closing thought
The pipeline gets your code to production. The delivery system ensures it gets there safely, consistently, and with confidence.
Investing in delivery engineering is one of the highest-leverage activities an engineering organization can undertake. It reduces risk, increases velocity, and gives teams the confidence to ship more frequently without fear.
That confidence is what separates organizations that move fast from organizations that move fast and break things.