ESC
← Back to blog

The Deployment Is Not the Release

· X min read
Operations Automation Architecture
AI Summary

At most companies, "deployment" and "release" are synonyms. The code goes to production and users see it -- same event, same moment, same risk. This conflation is the single biggest source of deployment anxiety in our industry. It's why teams deploy on Tuesday mornings instead of Friday afternoons. It's why releases are batched into big, scary events. It's why there are change advisory boards and deployment freezes and people sweating over a button click.

But deployment and release are not the same thing. A deployment is putting code on production servers. A release is exposing functionality to users. These are two independent operations, and separating them is one of the most powerful things you can do for your engineering organization's velocity and reliability.

Why the Conflation Is Dangerous

When deployment equals release, every deployment carries the full risk of user-facing change. You're not just asking "will this code run correctly on production infrastructure?" You're simultaneously asking "will this new behavior be acceptable to users?" and "can the system handle the traffic pattern this creates?" and "are there edge cases we didn't test?" All of these risks are bundled into a single atomic event. If anything goes wrong, you have to roll back everything -- the infrastructure change, the code change, and the user-facing change -- even if only one of those was the actual problem.

This bundled risk creates a perverse incentive to deploy less frequently. If each deployment is dangerous, the rational response is to minimize the number of deployments. But deploying less frequently means larger batch sizes. Larger batch sizes mean more changes per deployment. More changes per deployment means more risk per deployment. It's a vicious cycle that leads organizations to deploy weekly or monthly, each deployment becoming a major event requiring war rooms and rollback plans and on-call standby.

The teams that deploy most reliably are the ones that deploy most frequently. Not because they're more careful, but because each deployment is smaller, simpler, and lower risk. Separating deployment from release is how you break the vicious cycle and get there.

The Separation in Practice

Separating deployment from release means that code can exist in production without being active for users. The code is deployed -- it's on the servers, it's compiled, it's ready -- but it's not released. Users don't see it. Traffic doesn't hit it. It's dormant, waiting to be activated by a separate, independent decision.

This separation is achieved through several mechanisms, and they're not mutually exclusive:

Feature Flags

Feature flags are the most common and flexible mechanism for separating deployment from release. A feature flag wraps new functionality in a conditional check. The code is deployed to production, but the flag is off. The new code path is never executed until someone -- or something -- turns the flag on.

The power of feature flags goes far beyond simple on/off toggles. A well-implemented feature flag system supports progressive rollout: enable the feature for 1% of users, monitor error rates and performance, increase to 10%, monitor again, increase to 50%, and finally roll out to 100%. At any point, if something goes wrong, you flip the flag off. No rollback. No redeployment. The old code path is already there, ready to handle traffic.

This transforms the risk profile completely. Instead of "will this deployment work for 100% of users?" the question becomes "will this feature work for 1% of users?" The blast radius is controlled. The feedback loop is fast. And the remediation -- turning off the flag -- takes seconds, not the minutes or hours a rollback requires.

Dark Launches

A dark launch takes the concept further: new functionality is deployed and even executed in production, but the results are not shown to users. The new code path runs in parallel with the old one. Both process the same request. The old path returns the result to the user. The new path logs its result for comparison. You get real production data about the new code's behavior -- accuracy, latency, error rates -- without any user impact.

Dark launches are invaluable for high-risk changes. Migrating to a new database? Run reads against both the old and new database in parallel. Compare results. Find discrepancies. Fix them. When the new path matches the old path with sufficient accuracy, switch over with confidence. You've validated the change against real production traffic before a single user was affected.

This technique is particularly powerful for systems where synthetic testing can't adequately replicate production conditions -- which, to be honest, is most systems. Production traffic has patterns, edge cases, and data distributions that no test suite can fully anticipate. Dark launches let you test against the real thing safely.

Canary Deployments

Canary deployments route a small percentage of production traffic to the new version of a service while the majority continues hitting the old version. This is similar to a percentage-based feature flag rollout, but it operates at the infrastructure level rather than the application level.

The canary serves as an early warning system. If the new version has a memory leak, the canary's memory usage will climb while the old version stays flat. If the new version has a latency regression, the canary's p99 will diverge from the fleet average. If the new version has a bug that causes errors, the canary's error rate will spike before the error reaches the entire user base.

The best canary systems are automated. They promote or roll back based on predefined health metrics without human intervention. The deployment pipeline pushes the new version to the canary, waits for a defined observation period, compares metrics against thresholds, and either promotes to full rollout or rolls back automatically. The human is notified of the outcome but doesn't have to make the decision or execute the action.

The Psychology of Fearless Deployment

When deployment is decoupled from release, something remarkable happens to team psychology. Deployment anxiety disappears. Not gradually -- dramatically. Engineers stop thinking of deployment as a risky event and start thinking of it as a routine operation, like merging a pull request or running tests.

This shift happens because the consequences of a bad deployment are fundamentally different. In a coupled world, a bad deployment means users are affected, revenue is at risk, and someone is going to have a bad night. In a decoupled world, a bad deployment means... nothing visible happened. The code is in production but behind a flag. No users were affected. You can investigate the issue at your leisure, during business hours, with a cup of coffee.

I've seen teams go from deploying once a week with a 30-minute deployment ceremony to deploying multiple times a day with no ceremony at all. The change wasn't technical discipline -- it was the removal of fear. When deploying can't hurt users, there's no reason to be afraid of it. When you're not afraid, you deploy small changes frequently. When you deploy small changes frequently, each deployment is trivially simple. The virtuous cycle replaces the vicious one.

Reducing Batch Size

One of the most important consequences of separating deployment from release is that it enables -- and encourages -- small batch sizes. When you can deploy without releasing, there's no reason to batch changes together. A developer finishes a feature? Deploy it behind a flag. Another developer fixes a bug? Deploy it immediately. A third developer refactors a module? Deploy it alongside everything else.

Small batch sizes have cascading benefits:

Trunk-based development is the natural companion to deployment-release separation. When deployment is safe and frequent, there's no reason for long-lived feature branches. Developers commit to main behind feature flags. Main is always deployable. Branches are short-lived -- hours, not weeks. The entire development workflow becomes faster and simpler.

Release as a Business Decision

When deployment and release are separate, releasing becomes a business decision rather than a technical one. The code is already in production. It's been validated through dark launches or canary deployments. The question is no longer "does this work?" but "when should users see it?"

This gives product teams control over timing in a way that coupled deployment-release never allows. Want to coordinate a feature launch with a marketing campaign? The feature has been deployed for a week -- just flip the flag at the right moment. Want to soft-launch to a specific user segment for qualitative feedback before a broad release? Route the flag to beta users. Want to launch in one region first, then expand? Use geographic targeting in your flag configuration.

None of this requires engineering intervention at launch time. The engineering work -- writing the code, deploying it, validating it -- is done days or weeks before the release. The release itself is a configuration change that a product manager can make from a dashboard. This is what it looks like when engineering and product work together effectively: engineering delivers capability, product decides when and how to expose it.

The Operational Model

Making this work requires some operational discipline and tooling investment. You need:

Common Objections

"Feature flags add complexity." Yes. They do. But they add less complexity than rollback procedures, deployment ceremonies, change advisory boards, and deployment freezes. The complexity of feature flags is structured and manageable. The complexity of high-risk, infrequent deployments is chaotic and unpredictable.

"We don't deploy often enough for this to matter." You don't deploy often enough because deployment is coupled to release. Separate them and you'll deploy more frequently. Deploy more frequently and each deployment will be smaller and safer. The frequency follows the safety, not the other way around.

"Our system is too simple for this." If your system has users who depend on it, it's not too simple for this. Even a straightforward web application benefits from the ability to deploy a database migration today and activate the feature that depends on it tomorrow. Complexity isn't the trigger -- risk is. And any system with users has risk.

The goal isn't to deploy without risk. It's to deploy without fear. When you separate what goes to the servers from what goes to the users, deployments become boring. And boring deployments are the foundation of high-velocity engineering.

Stop conflating deployment with release. Deploy your code the moment it's ready. Release your features when the time is right. These are two different decisions, made by different people, at different times, for different reasons. The moment you internalize this separation, deployment stops being an event and starts being a process -- continuous, routine, and unremarkable. That's the goal. Not exciting deployments. Not heroic deployments. Boring ones. The kind nobody notices because nothing went wrong. Again.

Comments