Design to Be Released (formerly: CD)¶
Chapter Info
Calculating... Writing Progress: 70%
Change management is the process of planning, tracking, and executing software changes in a way that reduces risk and protects stability. It treats change as a lifecycle rather than an isolated action, covering validation, deployment, observation, and rollback. The goal is not to slow down delivery, but to make change predictable, observable, and reversible, ensuring that failures are contained and recovery is fast. Ultimately, it is about delivering value safely, consistently, and at scale.
This chapter explores Change Management as a risk management discipline. It covers automated testing and quality gates before deployment, progressive rollout strategies (canary/blue-green) to reduce blast radius, observability (metrics/logs/traces) and rollback mechanisms, dependency management and artifact management, provisioning through Infrastructure as Code (IaC), securing deployment parameters and secrets, and—especially in enterprise contexts—governance and compliance (approvals, controls, policies).
Introduction¶
Adaptive Software is the Heartbeat of Business [Change]¶
Software is no longer a static asset you build once; it is the dynamic engine of the modern enterprise. The true value lies not in the code itself, but in our capacity to evolve it.
This adaptability is the fuel of our industry. Whether we are reshaping a small corner of the architecture or launching a major new version, the ability to change is what drives the business forward. In a digital world, a company’s pulse is measured by how fluidly it can turn new ideas into working software. If the software stops evolving, the heartbeat stops.
The Essence of Release: Provisioning vs. Deployment [Provisioning vs. Deployment]¶
At its most fundamental level, the release process can be distilled into two distinct but complementary operations, each driven by its own set of artifacts:
Provisioning Resources: This involves using Infrastructure-as-Code artifacts (like Terraform plans or CloudFormation templates) to shape the environment. The goal is to instantiate the necessary topology—servers, load balancers, databases—that creates the foundation required to host the service.
Deploying Workloads: This involves taking the build artifacts (like Docker images, binaries, or JARs) and placing them onto the provisioned resources. This step installs the business logic into the prepared infrastructure, transforming inert resources into a live application.
The Shift: From Deployment to Release Management [Evolution] {1}¶
Early deployment practices treated change as a technical action: pushing an artifact to an environment, often as the final step of a CI pipeline. The focus was on execution—automation, speed, and repeatability. A deployment was seen as “the thing that changes production.”
As systems grew more complex, this model started to fail. A deployment describes that something happened, but not what state the system is now in, nor how to reason about risk, rollback, or accountability. In complex systems, multiple deployments, configuration changes, and artifact updates can interact in ways that are hard to reconstruct after the fact.
This led to a fundamental shift: the release became a first-class object. A release is not an event—it is a defined, versioned state. It is something you can name, reference, compare, promote, audit, and redeploy.
The Release¶
What Is a Release? [The Release]¶
A release represents a set of specific, versioned changes applied together to move a system to a new state. It defines what runs, how it is configured, and the rules that govern its movement across environments. A release exists independently of any single deployment action. A release brings structure to change. It has clear ownership, a defined lifecycle, and known rollback paths. Because it is identifiable and traceable, it becomes the unit through which risk is evaluated, monitored, and controlled.
A release is also an action: the deliberate act of moving a system from one state to another in order to deliver new features, updates, or security patches to production in a controlled and reversible way.
The "Paradigm Shift": Don't Plan Releases, Define Them [Define, Don’t Plan]¶
A release shouldn't be a calendar event—it should be a standard of quality. We define the rules that allow them to exist. In a modern architecture, releases are not orchestrated events but the continuous byproduct of a healthy pipeline. A release is simply code that has proven itself worthy of production.
A Release Does Not Mean the Same Thing to Everyone [Different Perspectives]¶
To understand how we moved from a simple model to a complex one, it is essential to recognize that a “release” evolved from a single technical action into a multi-dimensional concept. Its meaning now depends on responsibility, exposure, and risk.
For the company, a release is value delivery: the concrete translation of strategy into outcomes such as customer impact, revenue, reliability, and trust. It also implies clear visibility into what is live—features, capabilities, and exposed business value.
For a developer, a release is a moment of accountability, where assumptions meet production reality. It often carries tension, ownership, and the end of the illusion that releasing is “someone else’s job.”
For a security engineer, a release is an exposure point. The focus is on attack surfaces, control integrity, secret handling, and policy compliance. A release is acceptable only if the security posture is preserved or improved.
For an engineering manager, a release represents balance: delivering value while managing risk. Predictability, ownership, coordination, and fast reaction to failure are central concerns.
For a platform team, a release is a flow to design and operate—pipelines, environments, guardrails, and scalable mechanisms that enable autonomy without increasing systemic risk.
For governance and compliance teams, a release is an auditable event that must be traceable, approved, policy-compliant, and defensible.
For a group manager, a release is a coordination effort across teams, services, and shared dependencies, where success is measured by smooth execution without surprise.
A modern release exists precisely because all these perspectives must coexist. Release management is the discipline that aligns them into a single, coherent, and controlled lifecycle.
Release Is a Developer Responsibility [Developers Release] {1}¶
A release is not an operational task delegated to others; it is a developer-owned responsibility, because developers are the ones who introduce change, understand its intent, and must remain accountable for its impact in production. By making developers responsible for releases, we create a direct feedback loop between code creation and production impact, fostering ownership and accountability. This approach ensures that those who write the code are also responsible for its successful deployment, leading to better code quality, faster iteration, and a deeper understanding of production systems.
Release Governance Is a System Responsibility [System Governance]¶
A release is owned by developers, but it cannot rely on human coordination to be safe. Once intent is expressed, release execution must be governed by the system. A Release Manager acts as a control plane that enforces promotion rules, validates state transitions, applies organizational policies, and triggers rollbacks when needed. Developers remain accountable for the change; the system ensures that its execution is consistent, controlled, and reversible across environments.
Releases Are About Artifacts, Not Code [Artifacts vs. Code]¶
A release is fundamentally disconnected from source code. At release time, the code itself no longer matters—what matters are the immutable artifacts and the configurations that define the desired system state. Source code plays an indirect role: it is used earlier to produce artifacts such as container images, binaries, charts, or packages. But once these artifacts exist, are versioned, and are stored, the release no longer depends on the codebase itself.
Cloud Interaction Through the Control Plane [Control Plan] {1}¶
To understand the how of a release, we must first understand how modern systems are operated. Releases are no longer executed through direct, imperative actions performed by humans. Instead, they are expressed as intent and handed over to control planes—cloud APIs, infrastructure orchestrators, and platform layers—that continuously reconcile the desired state with reality. This shift changes the nature of a release: it is no longer about doing something to an environment, but about describing what the environment should become and letting autonomous systems enforce, validate, and maintain that state over time.
Autonomous Configuration, Everything Referenced [Declarative Interaction]¶
Once interaction with environments is mediated through control planes, execution fully shifts to the platform. The control plane receives a declarative configuration that expresses intent—what should run, which versions are allowed, and under which constraints. From there, the system becomes autonomous. The control plane decides how to act: it pulls the required artifacts, provisions or updates resources, enforces policies, and continuously reconciles reality with the desired state. A release no longer performs work directly; it merely updates intent, typically by pointing to new immutable artifacts. In this model, a modern release moves no payloads and executes no steps itself—nothing is transferred, everything is referenced.
Declarative Configuration Defines System State [Declarative State]¶
In a control-plane–driven architecture, the state of a system is not the outcome of past executions. It is defined entirely by the current set of declarative configurations. At any given moment, all active configurations—application manifests, infrastructure definitions, policies, and parameters—are interpreted by their respective control planes. Together, they describe the desired state of the system. The control planes continuously reconcile reality to match this intent. There is no hidden state and no authoritative execution history. If the configuration changes, the state changes. If the configuration is restored, the state converges back.
A Release Is a Declarative State Bundle [Release: Declarative Bundle]¶
A release can be defined as a named and versioned bundle of declarative configurations that together express the desired state of a system at a given point in time. A release groups all relevant sources of intent—application manifests, infrastructure definitions, platform-level configuration, policies, and parameters—into a coherent and identifiable unit. Each configuration remains interpreted by its own control plane, but the release provides a stable label that binds them together, making the system state explicit, comparable, auditable, and reversible.
Benefits of a Declarative Release Bundle [Declarative Bundle: Benefits]¶
Defining a release as a declarative bundle fundamentally changes how systems are operated and reasoned about. Because a release has a stable, versioned identity, monitoring becomes release-centric: metrics, logs, alerts, and incidents can be correlated to a specific release rather than to a vague moment in time. Because the bundle is immutable and reference-based, rollback is no longer a recovery procedure but a simple selection of a previous release, making reversibility deterministic and fast (This does not apply to stateful systems, such as databases). Because every change is expressed declaratively and bound to a release, traceability and auditability emerge naturally, allowing teams to understand what changed, why it changed, and who approved it. Governance and compliance stop being external processes and become enforceable properties of the release itself. In this model, safety, observability, rollback, and accountability are not added later—they are intrinsic benefits of treating releases as declarative state bundles.
Release Scope and Blast Radius [Release Scope]¶
The scope of a release is not fixed at design time; it is a contextual choice made at the moment a change is released. Well-designed systems make small, independent releases possible, and in most production scenarios, minimizing release scope is the safest option because it limits blast radius and reduces risk. Smaller release scopes also allow teams to work in parallel, make changes independently, and deliver value without blocking or coordinating with unrelated work. However, scope cannot always be reduced without consequence. When changes are tightly coupled or must remain consistent, grouping them into a single release can be safer than forcing artificial independence. There are also situations—such as onboarding new environments, bootstrapping ephemeral setups, or creating an initial system state—where releasing multiple services together makes sense, because there is no existing state to preserve. Release scope is therefore a risk and flow management lever, adjusted release by release, where developers balance independence, dependencies, context, and organizational impact rather than applying a universal rule.
Artifact Versioning as a Signal of Compatibility [Artifact Versioning]¶
Semantic Versioning is a model used to express compatibility at the artifact level. Artifacts define concrete contracts—such as APIs, schemas, or runtime behavior—and their versions communicate how those contracts evolve over time. A major version indicates a breaking change, a minor version signals backward-compatible evolution, and a patch version represents a backward-compatible fix. These semantics are meaningful because artifacts are consumed independently and can break their consumers.
A release bundle, however, operates at a different level: it represents a logical set of declarative configurations that together define a desired system (or subsystem) state. Within that set, it simply references specific artifact versions and binds them into a coherent snapshot. The version or number assigned to a release serves only to identify that snapshot so it can be tracked, deployed, audited, or rolled back.
Making Declarative Releases Stable, Reusable, and Portable [Intent vs Execution]¶
Modern release systems distinguish between intent and execution. They describe what a system should become, without immediately deciding how or with which concrete values it will be realized. Some details are stable and structural, others are variable and context-dependent. Keeping these concerns separate is what makes releases predictable, reusable, and adaptable across environments.
Separating Definition from Variable Details [Variable Details]¶
This separation is not an implementation detail; it is a design constraint that preserves the stability and reusability of declarative systems as they scale.
Artifact versions and environment-dependent parameters are variable details that should not be embedded in release definitions. They change frequently, evolve independently, and depend on execution context rather than intent. By externalizing these details and resolving them at execution time, release definitions stay independent from runtime concerns. Control over these details is deliberately inverted: the release defines structure and intent, while execution supplies the concrete values.
Execution binds a stable release definition to concrete artifact versions and an environment-specific parameter set.
Why Declarative Configurations Must Not Embed Implementation Details [Intent vs Implementation]¶
This separation is a modeling choice that becomes increasingly valuable as systems grow in scale, complexity, and organizational reach. Declarative configurations are meant to express intent, not to encode implementation decisions. They describe what the system should become, not how it should be realized in a specific context or at a specific moment in time. These configurations can take many forms—Kubernetes manifests, Helm charts, infrastructure-as-code definitions, platform policies, or other declarative specifications—and are interpreted by control planes to converge the system toward a desired state. While some tools provide local mechanisms to abstract details, such as templating or values files, these mechanisms remain scoped to individual components. At the release level, implementation details such as artifact versions and environment-specific parameters must be externalized and resolved separately. Embedding such details directly into declarative configurations couples intent to context and time, reducing reuse, increasing duplication, and weakening the stability required for controlled, repeatable execution.
Why Artifact Versions Must Not Be Hardcoded [Version Decoupling]¶
Artifact versions are temporal decisions, not structural ones. They evolve frequently, follow their own lifecycle, and often carry compatibility signals that are meaningful only at the artifact level. Hardcoding versions into declarative configurations forces a new definition for every build or update, leading to an explosion of near-identical releases. In such a model, releases become activity logs rather than stable units of intent. By keeping artifact versions external and resolving them at execution time, release definitions remain stable, reusable, and meaningful across promotions, rollbacks, and environments.
Why Parameters Must Not Be Hardcoded [Runtime Context]¶
Parameters represent execution context, not system intent. They vary across environments, change independently from structure, and evolve based on operational needs. Hardcoding parameters into declarative definitions binds those definitions to a specific environment, forcing duplication and reducing reuse. When parameters are embedded, declarative configurations stop being generic expressions of intent and become environment-specific artifacts. Parameters therefore belong to execution context and must be resolved separately from the release definition.
Safe Change¶
Safe management refers to the disciplined practice of introducing change in a controlled, observable, and reversible manner. It focuses on reducing risk by treating every deployment as a managed change rather than a one-time technical action.
Change is inevitable. But in a complex, interconnected world, even small adjustments can have far-reaching consequences. That’s why we speak of safe change—a deliberate way to introduce improvements without putting the system at risk. Safe change doesn’t mean “no risk,” but rather “managed risk,” supported by testing, safeguards, clear communication, and, above all, the ability to roll back if needed. It’s the art of moving forward without breaking things.
No Change Is Without Risk¶
Every change—no matter how small, well-tested, or routine—carries risk. A minor configuration tweak, a dependency update, or even a simple DNS change can trigger unexpected side effects. Systems are complex, and their interactions often defy prediction. What worked in staging might break in production due to unseen variables. The idea of a “safe” change creates a false sense of security. Instead, every change should be treated with humility, monitored carefully, and backed by a clear rollback plan. Safety isn’t in the change itself—it’s in how you prepare for what could go wrong.
Risk Management Through Universal Principles [Risk Management] {1}¶
Safe change is fundamentally about risk management. Every deployment introduces uncertainty, regardless of how small or well-tested the change appears. The objective is not to eliminate risk—which is impossible—but to control, limit, and contain it.
To manage risk effectively, we rely on universal principles: move in small steps, explore and experiment first, expose changes incrementally, isolate issues when they occur, choose the right timing, avoid irreversible decisions, minimize human error through automation, maintain clear accountability, and never accumulate multiple risks at once. These principles form the foundation of safe change practices, transforming deployment from a risky gamble into a controlled, predictable process.
Identify What Can Go Wrong [What Can Go Wrong]¶
Effective risk management begins by pinpointing exactly what can go wrong. In software delivery, every change inherently introduces uncertainty. Before discussing tools or practices, we must clearly define the risks we aim to mitigate. Fundamentally, these risks fall into two categories: deployment failures, which leave the system in an unhealthy state, and application failures, where code deploys successfully but functionality is flawed. Safe change practices exist to address both—preserving system stability when deployments falter, and limiting the "blast radius" when the application misses the mark.
Note: This section focuses primarily on the problems of application failure. Issues related to deployment failure will be addressed in the section "Managing Deployment Failures".
Clear Ownership: The Prerequisite for Safe Changes [Clear Ownership]¶
Clear ownership is the foundation of safe change management. Every deployment requires a single, identifiable owner who understands the change, its purpose, and its potential impact. Without this, ambiguity leads to delayed decisions and contentious rollbacks during incidents. Ownership equals accountability: the owner is responsible for monitoring the release, responding to issues, and deciding when to roll back. This must be established before deployment, not discovered during an outage. By clearly identifying who authorized the change and who owns the service, you eliminate confusion and ensure rapid, decisive action when things go wrong.
Note
- Each team has its own life cycle for deployment, particularly when discussing micro front-ends.
Automation: The Key to Safe Change [Automation]¶
Manual steps are the weak link in safe change management. They introduce variability, human error, and hidden assumptions that compromise stability. To ensure safety, deployments must be deterministic: fully automated, auditable, and consistent across every environment. By replacing "tribal knowledge" with code, you ensure that the path to production is rigorous and safe.
Safety Is in Preparation, Not in the Change [Safety in Preparation]¶
Safety isn't in the change itself—it's in how you prepare for what could go wrong. No deployment can be guaranteed safe simply because the code looks correct or tests passed. True safety comes from preparation: comprehensive monitoring to detect issues early, automated rollback mechanisms ready to activate, clear ownership and response procedures, and thorough testing of failure scenarios. The most dangerous deployments are those that appear safe but lack proper safeguards—when something goes wrong, there's no plan, no visibility, and no quick recovery path. By focusing on preparation rather than assuming safety, teams build resilient systems that can handle unexpected failures gracefully.
The Principle of Configuration Isolation [Configuration Isolation]¶
Production and non-production configurations must remain strictly independent to prevent "configuration leakage." It is vital to ensure that settings used for testing new security measures or technical migrations can never inadvertently migrate to the live environment. Without this total separation, a simple failure to clean up after a test can apply blocking restrictions to all legitimate users, turning a minor handling error into a major service outage. We will explore hierarchical configuration patterns that enforce this separation by design in a later section.
Safe Releases Require Safe Places to Experiment [Experiment]¶
A release can only be safe if experimentation happens outside the path to production. When developers are forced to test ideas, integrations, or risky changes directly in the main release flow, instability accumulates and release quality degrades. Safety is not achieved by slowing releases down, but by ensuring that unsafe exploration never contaminates the production release lifecycle. A safe release system must therefore provide alternative spaces where uncertainty is allowed.
Isolated Environments Enable Safe Experimentation [Isolated Environment]¶
Developers must be able to deploy code or artifacts into isolated environments—ephemeral or on-demand—without polluting release definitions or blocking the production flow. These environments allow developers to validate assumptions, test integrations, and experiment freely, while keeping the release path clean, deterministic, and auditable. By separating experimentation from promotion, the system preserves release safety while still enabling rapid learning. Safe releases are not achieved by restricting developers, but by giving them controlled spaces to explore without risk to production.
Small Incremental Releases [Tiny Steps] {1}¶
Releasing small, incremental changes is one of the most effective ways to reduce risk. Smaller changes are easier to understand, easier to test, and easier to roll back. When something goes wrong, the blast radius is limited and root cause analysis becomes straightforward. Incremental releases transform failures from catastrophic events into manageable incidents.
Developer Responsibility: From Merge to Production [Change Risk]¶
Once your code is merged into master, it becomes the developer responsibility to shepherd it all the way to production. This ownership doesn't end at the merge—you must ensure your changes are deployed, validated, and stable in production.
When changes accumulate in master without deployment, urgent fixes risk being bundled with unrelated, untested changes, increasing the likelihood of production issues. A proper Code Release pipeline automates this journey, deploying changes incrementally and reducing risk. Without automation, maintaining deployment discipline is essential to keep master clean and deployable at all times.
Responsive Development: Frequent Commits and Releases [Commit Often]¶
The "Commit Early, Commit Often, Frequent Releases" principle is a fundamental best practice in software development that emphasizes continuous integration and delivery. This approach encourages developers to make small, incremental changes and commit them regularly, instead of waiting to bundle large, complex updates. By committing early and often, teams can more easily track progress, swiftly identify and resolve conflicts, and maintain a clear history of the project's evolution. Frequent releases enable stakeholders to provide timely feedback and allow teams to adapt quickly to changing requirements or market conditions. This methodology fosters collaboration, reduces the risk of significant integration issues, and helps maintain a steady, manageable development pace, ultimately leading to higher-quality software and more satisfied users.
Maintaining the parity with production [Production Parity]¶
Production parity is about keeping the gap between non-production environments and production as small as possible. When configurations, dependencies, data shapes, and operational behaviors stay aligned, tests and validations performed earlier in the pipeline remain meaningful. As this gap grows, confidence gained before release becomes increasingly misleading and risk is merely deferred to production. Parity goes beyond infrastructure and versions; it also includes deployment processes, monitoring, and failure handling. By releasing continuously and evolving all environments together, teams reduce surprises and make production a predictable extension of the delivery flow rather than a fundamentally different system.
The Limits of Production Parity [Limits of Parity]¶
Maintaining a non-production environment that is a pixel-perfect mirror of production is neither economically viable nor practically achievable. Differences in hardware, storage architecture, and scale are unavoidable. Furthermore, strict data privacy regulations and the chaotic nature of live traffic mean that pre-production environments will always lack the 'noise' of the real world. As a result, despite our best efforts and rigorous testing standards, a specific category of bugs will inevitably manifest only in production. These are not oversights; they are the result of inherent environmental limitations. These issues are dormant in staging and only emerge under the specific pressure of real-world load, data integrity, and system interactions.
Controlling Risk Through Experimentation in Production [Test in Production]¶
A controlled risk is superior to an uncontrolled disaster. Once pre-production limits are reached, the choice is no longer between risk and safety, but between contained risk and unbounded failure. Critical properties like resiliency and elasticity cannot be fully validated outside of production. Replicating real traffic, timing, and data interactions in test environments is often impossible or prohibitively expensive. This creates a dilemma: avoiding production experimentation leaves critical risks unexamined until they trigger a crisis, yet uncontrolled experimentation is unacceptable.
The only viable solution is controlled experimentation. By introducing deliberate, limited stress—such as fault injection or load shaping—teams can validate system behavior under real conditions while strictly limiting the "blast radius." In this model, production experimentation is not reckless; it is a disciplined strategy to manage risks that cannot be addressed any other way.
Testing in production is an evolution. What was once considered a bad practice is now a necessity, simply because modern systems are too complex to fully validate in a sterile environment
Minimizing Deployment Risk: Canary Deployments [Canary deployments]¶
The most effective way to prevent widespread outages is to limit the "blast radius" of a change. A Canary deployment routes a tiny fraction of real traffic to the new version while the rest of the system remains untouched. This acts as an early warning system in production. If the new version causes errors or latency (if it is "toxic"), the impact is contained to a negligible subset of users, and the change is instantly rolled back. The majority of the system is never exposed to the risk.
Controlled Gradual Rollout of Change [Gradual Rollout] {1}¶
Extending canary logic to the remaining 99% of traffic minimizes risk. Instead of a 'big bang' release, deployments advance through segments—starting with low-risk groups (e.g., non-paying users) and reaching high-value customers only after stability is proven. By segmenting by value and geography with strategic delays, organizations ensure issues are resolved in low-stakes environments before impacting critical users.
Gradual Rollout Across All Delivery Systems [Rollout for All Systems]¶
Gradual rollouts shouldn’t stop at customer-facing services; they are vital for your entire development ecosystem. Treat CI/CD pipelines, CLIs, and test suites as products, with developers as your customers. If these internal systems break, your ability to ship fixes breaks, paralyzing the organization. Unreliable tests leave you flying blind. Therefore, your internal ecosystem is SEV-0. To ensure resilience, you must apply the same progressive rigor to your internal tools, platforms, and policies as you do to your customer-facing apps.
The Conflict Between Gradual Rollouts and High-Frequency Releases [Rollout vs. Frequency]¶
There is an inherent conflict between the safety mechanism of gradual rollouts and the agility of high-frequency micro-releases, primarily because they operate on opposing temporal scales. While gradual rollouts intentionally slow down the exposure of new code to limit the "blast radius" of errors, this process effectively locks the deployment pipeline, preventing subsequent small updates from shipping immediately. This necessitates an undesirable accumulation of changes behind the active rollout, forcing us to deploy larger, riskier batches instead of the intended small, atomic units.
Decoupling Deployment from Release: Feature Flags [Feature Flags]¶
One effective way to resolve the friction between safety and velocity is to decouple deployment from release using Feature Flags. Instead of relying on slow rollouts that congest the pipeline, this strategy involves deploying code to 100% of the production environment immediately, but keeping the new functionality dormant behind a toggle. This approach clears the deployment pipeline in minutes rather than days, allowing the team to continue shipping frequent micro-releases without interruption. The "gradual" aspect is then managed purely through software configuration, enabling you to ramp up user exposure to the new feature independently of the deployment schedule, thus achieving both high-frequency delivery and risk-controlled releases simultaneously.
Observing Before Activating: Logging-Only Mode [Logging Only Mode]¶
Before fully activating new logic, deploy it in a logging-only mode where the code executes but does not produce real effects. The new path runs alongside the existing behavior, logging what it would do without actually doing it. This provides a safe observation window to analyze the impact of the change on real production traffic before committing to it.
This approach is particularly valuable when:
- Replacing existing logic: Run both old and new implementations in parallel, compare their outputs in logs, and validate that the new version produces correct results before switching.
- Introducing risky business rules: Observe how new validation rules, pricing calculations, or filtering logic would affect real requests without impacting users.
- Migrating data flows: Log the differences between legacy and new data transformations to ensure consistency before cutover.
The logging-only phase transforms deployment from a leap of faith into an evidence-based decision. Teams can review logs, detect edge cases, and build confidence before activation—turning potential production incidents into controlled observations.
Decomposing Change Into Safe Steps [Release as Migration]¶
Every release is a state transition. Sometimes, the gap between the current and target states is so wide that the release becomes a migration rather than a simple update. When architecture or behavior changes significantly, the risk lies in the transition itself, not the destination. Safety rarely comes from a single move. Instead, deliberately decompose that transition into smaller, controlled steps—each easy to validate and roll back. Apply the discipline of architectural migrations to every release: identify stable intermediate states and move through them incrementally. Ultimately, a release is not just deploying code; it is the controlled evolution of your system's state.
Two-Phase Deployment: Separating Preparation from Activation [Two Phase Deployment]¶
Two-phase deployment ensures safety by separating preparation from activation. Instead of introducing a risky change all at once, first prepare the system to tolerate the new state without relying on it. Only activate the new behavior once this preparation is complete.
This approach reframes deployment as a deliberate transition between compatible states, ensuring that rollback remains safe at any point. The goal is not just mechanical execution, but intent: design your changes so that moving forward and backward are both valid paths, ensuring the system never enters an unsafe intermediate state.
A change is Safe Only if it can be undone [Reversibility]¶
Some changes create irreversible states: incompatible schemas, destructive data migrations, or protocol changes that break backward compatibility. Once these doors are crossed, rollback becomes risky or impossible. This is not a CI/CD problem but a design problem. If a change cannot be safely undone, it should not be deployed as a single step
Rollbackability Through Upgrade–Downgrade Testing [Rollbackability]¶
Rollbackability is the ability to revert a software deployment to a previous version without causing service disruption or data loss. One effective way to ensure rollbackability is through upgrade–downgrade testing: deploying a change forward and then rolling it back in a test environment that mirrors production. Companies like Amazon use this approach to catch hidden issues early and ensure rollbacks are safe and reliable.
Release Timing: When Time Changes Risk [Release Timing]¶
Timing is critical. A routine change can become catastrophic if deployed during high-stakes windows like Black Friday or peak traffic hours. Because context fundamentally alters risk, organizations must enforce automated guardrails at the platform level. These controls should strictly block releases based on business calendars, removing the reliance on manual reminders that can be easily ignored.
The Moratorium Paradox: Protection vs. Accumulation [Protection vs. Accumulation]¶
In the context of software delivery, a "moratorium period" is defined as a designated timeframe during which all non-essential deployments and updates to the production environment are strictly suspended. Its primary objective is to guarantee absolute system stability and mitigate risk for the client during critical business cycles. However, this right to "freeze" the system must be exercised with restraint and should not be abused. While the production environment remains static, development often continues, creating a widening gap—a silent accumulation of technical debt—between the evolving features in development and the frozen state of production. Consequently, the longer the moratorium, the more painful the eventual reconciliation period becomes, turning a standard deployment process into a problematic and high-risk integration effort
Ensuring Release Quality¶
Release quality cannot be an afterthought—it must be built in from day one of development. But quality can only be measured through testing. Without tests, there is no objective way to assess whether code meets requirements, performs correctly, or maintains reliability. Testing provides the metrics and evidence needed to validate quality throughout the development lifecycle, making it the foundation of any quality assurance strategy.
Well Defined Criteria [Quality Criteria]¶
Quality gates must be explicit, measurable, and automatically enforced at the platform level. Manual checks and good intentions are not enough—if criteria are not met, teams must be prevented from deploying to production. Automated gates should block deployments that fail to meet predefined thresholds for test coverage, performance benchmarks, security scans, or other quality metrics. By making criteria mandatory and non-negotiable, organizations ensure that quality standards are consistently applied across all releases, eliminating the risk of exceptions that compromise system reliability.
Ensuring Quality Only Through Robust Testing [Test First]¶
Investing in testing must precede any investment in DevOps. Many projects mistakenly expect DevOps to compensate for inadequate testing, but this is a fundamental error. If tests are weak, no amount of sophisticated release architecture will save the project. Release quality begins with strong, reliable tests—solid testing processes must be prioritized before diving into complex DevOps implementations. The integrity of any deployment pipeline is only as strong as the tests that validate it.
Code Coverage [Coverage Metrics]¶
Code coverage metrics reveal how much of the codebase is exercised by tests. While high coverage doesn't guarantee quality, low coverage signals significant risk. Setting minimum coverage thresholds as a quality gate ensures that critical code paths are tested. However, coverage alone is insufficient—what matters is the quality and relevance of the tests, not just the percentage. Coverage metrics should serve as a baseline indicator, combined with other quality measures to ensure comprehensive testing.
Integration Tests [Integration Validation]¶
Integration tests validate that multiple components work together correctly. Unlike unit tests that isolate individual components, integration tests verify interactions between services, databases, APIs, and external dependencies. These tests are essential for catching issues that only emerge when components are combined—API contract mismatches, data flow problems, configuration errors. Integration tests must run in environments that closely mirror production, ensuring that the same interactions validated in testing will work in production.
Proving Artifacts Were Deployed in Lower Environments [Artifact Promotion]¶
Before an artifact can be promoted to production, it must have been successfully deployed and validated in lower environments, typically starting with development. This requirement ensures that artifacts are tested in real deployment scenarios before reaching production. The release system must enforce this promotion path automatically, blocking production deployments of artifacts that haven't been deployed to lower environments first. This creates a natural progression where artifacts are proven stable at each stage before advancing, eliminating the risk of introducing untested code to production.
Centralizing Artifact Lifecycle Management [Artifact Registry]¶
Defining quality criteria is not enough—you need a centralized mechanism to implement and enforce them. Artifacts undergo extensive validation throughout their lifecycle: security scans, secret detection, dependency checks, and deployment validation across environments. These validations happen asynchronously and continuously—vulnerability scans run daily, and deployment status must be tracked across multiple environments.
A centralized Artifact Registry tracks every artifact from creation in CI until retirement, maintaining a complete record of deployments, validations, and security scans. Without this centralization, quality gates become impossible to enforce. The registry serves as the single source of truth that transforms quality criteria from aspirational goals into enforceable, automated gates.
Proving Rollbackability [Rollback Validation]¶
Rollbackability must be demonstrated, not assumed. The only reliable way to verify it is to deploy a change forward and then explicitly roll it back in an environment that mirrors production. Upgrade–downgrade testing exposes hidden assumptions and unsafe transitions that would otherwise remain undetected. If rollback fails during testing, the release was never safe in the first place.
Make Changes Traceable [Traceable Changes]¶
Traceability is fundamental to safe change management. When a production incident occurs, teams must quickly identify what changed, when it changed, who made the change, and why. Without proper traceability, debugging becomes a time-consuming detective game—you might spend 40 minutes just trying to understand what the last change was before you can even begin to revert it.
Every change must be linked to its source: the commit that introduced it, the pull request that reviewed it, the deployment that released it, and the person who authorized it. This traceability chain enables rapid incident response, effective root cause analysis, and confident rollbacks. When every change is traceable, failures become learning opportunities rather than mysteries.
Continuous Monitoring [Production Monitoring]¶
You cannot manage risk without visibility. Continuous monitoring provides real-time feedback on the impact of a change once it reaches production. Metrics, logs, and alerts enable teams to detect anomalies early, often before users are affected. Monitoring shifts risk detection from reactive incident handling to proactive system observation, transforming production from a black box into a transparent, observable system.
The Configuration Triad: Static, Dynamic, and Secrets [Configuration Triad]¶
According to The Twelve-Factor App methodology, configuration encompasses everything likely to vary between deployments. However, to manage these effectively, we must distinguish them by origin and lifecycle. In the following section, we classify configuration into three types: Static Parameters (explicitly defined by the user), Dynamic Parameters (generated by processes), and Secrets (sensitive data requiring protection).
Static Parameters¶
Defining User Intent [User Intent]¶
Static Parameters represent the configuration values explicitly defined by engineers before the deployment pipeline even runs. They are deliberate human decisions designed to shape the environment, such as choosing a t3.nano instance for development to save costs versus a t3.xlarge for production performance. These parameters are predictable, versioned alongside your infrastructure code, and serve as the blueprint of your intent.
The Reality: Configuration Sprawl [Configuration Sprawl]¶
Too often, static parameters are deeply embedded within implementation logic. We find them hardcoded inside Helm templates, buried in Terraform resource blocks, and scattered throughout Bash or Python scripts. This dispersion makes it impossible to have a consolidated view of the environment. Simply asking, "What was the exact state of our variables during last week's deployment?" becomes a forensic project requiring a manual audit of the entire codebase.
Single Source of Truth [Single Source of Truth]¶
The solution requires a fundamental shift: separating all static parameters from deployment code and consolidating them into a single entity that serves as the sole source of truth. This centralized repository is designed for agnostic consumption; whether the data is required by a Helm chart, a Terraform module, or a script is irrelevant. By centralizing these inputs, we establish a holistic view of the system's intended state, turning configuration history into an accessible and auditable asset.
Runtime Injection of Versioned Configuration [Version & Injection]¶
The central repository treats configuration as version-controlled, immutable code. At execution, a specific version is injected into the runtime. This keeps deployment artifacts invariant across environments while ensuring a fully auditable system state. Rollbacks become simple: just pair the previous artifact version with the previous configuration version.
The Override Pattern¶
Effective management relies on designing for defaults. By defining a generic configuration file populated with standard values, you establish a baseline for the majority of environments. Specific targets, like Production, are managed through 'deltas'—files containing only necessary overrides. This method isolates critical environment differences, eliminates duplication errors, and accelerates new infrastructure provisioning.
Dynamic Parameters¶
Dynamic Parameters: The Output of External Processes [Process Output]¶
Dynamic parameters are defined not by user input, but as the output of an external process. They represent data unavailable at design time, materializing only as a result of system execution—typically triggered by resource provisioning. These values are critical, as applications rely on them to locate and connect to their infrastructure dependencies.
Capturing Reality vs. Defining Intent [Reality vs Intent]¶
While Static Parameters represent the Desired State (what engineers want the system to be), Dynamic Parameters capture the Actual State (what the system became after execution). You cannot version the future; you can only discover it. A unique Load Balancer DNS name or a VPC ID generated by a cloud provider does not exist until the infrastructure is provisioned. Therefore, these values represent the immutable reality of the live environment.
Strategies for Retrieving Dynamic Context [Retrieving Dynamic]¶
Since dynamic parameters are generated at runtime, they cannot be stored in Git like static configuration. Consequently, the deployment machinery requires a reliable strategy to retrieve these values and pass them to the dependent workload. We will explore three distinct strategies to address this challenge below.
Strategy 1: Direct Pipeline Handoff (Coupled) [Direct Pipeline]¶
The simplest method occurs entirely within a single pipeline execution. In step A, a process (like Terraform) creates infrastructure and outputs values (e.g., a database endpoint). These values are passed immediately "on-the-fly" to step B (application deployment) via memory or temporary files.
The Downside: This creates tight coupling. You cannot deploy the application without running the infrastructure code. It limits modularity and makes retries difficult, as the data is ephemeral—if the pipeline crashes, the state is lost.
Strategy 2: Live Cloud Interrogation (Discovery) [Discovery]¶
This approach fundamentally decouples the provisioning lifecycle from the application deployment. Unlike the "single pipeline" strategy, these two processes operate independently, sharing no memory, temporary files, or execution context. To bridge this gap, the deployment pipeline must "discover" its environment. Instead of receiving values passively, it actively interrogates reality by using Cloud APIs to query the live infrastructure, typically filtering for resources by specific Tags assigned during creation.
The Downside: This introduces a heavy dependency on external APIs. It suffers from scalability and performance issues (latency, rate-limiting/throttling by the cloud provider) and fragility—if a tag is modified manually, the deployment breaks.
Strategy 3: Dedicated State Persistence (The Recommended Pattern) [Dedicated State]¶
The most robust method uses a dedicated intermediate storage service. The process responsible for creating resources (e.g., Terraform) explicitly "publishes" only the relevant outputs to a persistent data store (like a Key-Value store or a database). This makes consumption effortless: any downstream system or user simply needs to query the store to retrieve the value, decoupling access from creation.
The Advantage: This offers selectivity and decoupling. We choose exactly which data points are exposed to the wider system. Furthermore, as we will see later, this method is the key to the "Unified Store" architecture: it allows us to consolidate these dynamic values alongside static parameters behind a single, consistent API, hiding the complexity of their origin.
Secret [Secret]¶
Secrets encompass all sensitive data representing identity or authorization. Due to their critical nature, they require specialized lifecycle management that strictly categorizes them by consumer. A robust release architecture must differentiate between the operational credentials required to orchestrate the deployment and the runtime secrets consumed by the application.
Shift-Left: Secret Scanning [Secret Scanning]¶
Automated artifact scanning is a non-negotiable prerequisite. Before any deployment logic executes, the pipeline must validate artifact integrity to ensure no credentials have been committed to source code. By integrating detection tools immediately after the commit stage ("Shift-Left"), the release process acts as a gatekeeper, neutralizing security risks before compromised artifacts ever enter the delivery chain.
Operational Secrets for the Release Process [Operational Secrets]¶
The release machinery requires its own credentials to function. "Process Secrets" are keys utilized by the CI/CD infrastructure itself—such as Terraform Service Account keys or Docker Registry tokens. These operate strictly at the infrastructure layer, granting the "deployment robot" authority to orchestrate the release.
Delivering Secrets to the Application [Secrets for Workload]¶
The release pipeline is frequently used to deliver secrets to the application. In this model, the process acts as a courier: it retrieves sensitive data (like DB passwords) and injects them into the workload at deployment time. This ensures the application starts with immediate access to its dependencies. However, this implies that the deployment pipeline itself must handle plaintext secrets. More critically, it creates tight coupling: you cannot deploy the application without running the provisioning logic to retrieve the keys. Furthermore, it binds the secret's lifecycle to the release: for example, to rotate its keys, you are forced to redeploy the entire workload.
Decoupling Secrets from Deployment: Empowering Workload Autonomy [Secret Decoupling]¶
Coupling secret injection with the release process is an architectural anti-pattern; it burdens the deployment pipeline with sensitive data irrelevant to artifact delivery. To resolve this, we must transfer the responsibility of credential retrieval from the external release mechanism to the workload itself. The application becomes autonomous, proactively fetching its own secrets at runtime. This shift is implemented via Workload Identity, allowing the service to authenticate directly with vaults or resources, ensuring the release process remains a simple carrier, blind to the keys it transports.
Prioritizing Identity-Based Access [Identity-Based Access]¶
The pinnacle of secure release architecture is the complete elimination of shared secrets in favor of Identity-Based Access. In this model, the workload utilizes cryptographic identities (such as SPIFFE SVIDs, Kubernetes Service Accounts, or Cloud IAM profiles) to establish mutual trust (mTLS) directly with dependent services. By relying on "who the workload is" rather than "what secret it holds," we remove the risk of credential theft entirely, as authentication is handled strictly at the protocol layer without the application ever needing to manage, read, or rotate a password.
Fallback to Ephemeral Credentials [Ephemeral Credentials]¶
When direct identity-based authentication is technically infeasible—often due to legacy databases or third-party APIs that strictly mandate credential exchanges—the strategy must shift to Ephemeral Credentials. Instead of reverting to risky static keys, the system leverages the workload's identity to mint short-lived tokens or "dynamic secrets" that exist only for the duration of a specific session or transaction. This approach drastically minimizes the security blast radius; even if a credential is leaked, its strictly limited lifespan renders it useless to attackers almost immediately, effectively neutralizing the threat of long-term exposure without manual rotation.
The Fragmentation Challenge: Scattered Truth [Fragmentation Challenge]¶
The Burden of Assembly In a fragmented architecture, the Release Pipeline is forced to act as a complex integration broker. Because configuration data is hosted in three incompatible systems—Git for static files, Cloud APIs for dynamic resources, and Vaults for secrets—the deployment logic must implement specific connectors for each. This creates a fragile "glue layer" where the pipeline is burdened with the heavy lifting of fetching, parsing, and merging these disparate streams at runtime, merely to construct a usable application state.
The Unified Configuration Store [The Unified Store]¶
The ultimate evolution of configuration management is to completely decouple the origin of a parameter from its consumption. By implementing a Unified State Store, we consolidate all three configuration categories—Static, Dynamic, and Secrets—into a single, queryable layer. In this model, Git remains the authoring interface for humans, preserving version history and peer reviews for static parameters; however, a synchronization process immediately replicates these values into the Store upon merge. Simultaneously, infrastructure pipelines inject dynamic values, and security workflows populate the Vault.
For the release pipeline, this complexity is invisible. It interacts with a single API or abstraction layer to retrieve its entire configuration state, irrelevant of whether a value was typed by a developer in a YAML file, generated by a cloud provider, or encrypted by a security officer.
Architecture¶
As our applications evolve into distributed, resilient systems, our delivery mechanisms must follow suit. The challenge is no longer just how to automate, but how to scale without bottlenecks while containing the "blast radius" of change. This section outlines the shift from centralized command-and-control to a distributed architecture that decouples execution from governance, ensuring that delivery scales horizontally and a failure in one environment never compromises the whole.
The Monolithic Release Engine [Monolithic]¶
In a monolithic architecture, a single engine drives deployments using an imperative "Push" model, forcing commands directly into all targets. This centralization concentrates risk: because the engine requires direct access to every environment, a failure or blockage in one stage (like Production) propagates globally. This tight coupling between governance and execution makes the system rigid, fragile, and difficult to evolve without impacting the entire organization.
The Distributed Control Plane [Distributed Control Plane]¶
To move beyond rigid "Push" systems, we invert the flow using an event-driven "Pull" model. Instead of queuing deployments on a bottlenecked central engine, autonomous agents continuously monitor a source of truth to retrieve the target artifacts. This architecture scales horizontally: execution capacity grows naturally with the infrastructure, eliminating performance degradation. Simultaneously, it drastically reduces the "blast radius," ensuring that a failure in Production is isolated and cannot block Development, even if the central hub is offline.
Evolutionary CD: The Pipeline as a Product [Evolutionary CD]¶
We treat the release engine as a release itself. The deployment agent is no longer a static tool, but a versioned artifact that follows its own lifecycle. We can deploy a new "Agent v2.0" to the Development environment to validate new logic, while Production remains safely on "Agent v1.0". This approach applies the same rigor, testing, and promotion strategies to the delivery mechanism as we do to the applications it delivers.
Centralized Governance [Centralized Governance]¶
Execution is distributed, but authority is centralized. Each environment runs its own autonomous release agent, yet no change can be executed without prior authorization from a central governance hub acting as a control tower. Before a release is allowed, global constraints are evaluated—security posture (for example newly disclosed vulnerabilities), compliance rules, freeze windows, and business policies. Once approved, execution remains fully local: agents apply changes independently, using local knowledge and local health signals, and failures are contained to their environment. All states, decisions, and outcomes are reported back to the hub, providing a synchronized global view of what is running where, without turning governance into an execution bottleneck.
Deployment Failures and Their Impact on Service Availability [Deployment Failures]¶
Deployment Failures Are Expected [Expect Failure] {1}¶
Failures are not exceptional events but an inevitable outcome of change. Treating them as anomalies leads to panic and poor decision-making when stability is needed most. Mature systems assume failure and prepare for it by shifting focus from prevention alone to rapid detection and recovery. Organizations that accept this reality build resilience through preparation rather than relying on a false confidence in perfect deployments.
Deployment Failure Type [Failures Type]¶
Deployment failures are not uniform. They differ by where the failure occurs and how it affects the running system. At a high level, failures fall into three categories.
Control-plane failures occur when the deployment mechanism itself fails while the running system may remain healthy. Examples include pipeline crashes, authorization errors, or orchestration timeouts. These failures affect delivery, not availability.
State-transition failures occur when the system partially moves toward a new state and ends up inconsistent or degraded. Some components are updated while others are not, leading to version skew, broken dependencies, or undefined behavior. The system is running, but not in a coherent state.
Runtime-impacting failures occur when the deployment succeeds, but the new state cannot sustain live traffic.
Check Service Health First [System Health First]¶
An error message rarely tells the full story, so decisions must be driven by system health indicators rather than logs alone. Evaluate availability, latency, error rates, and saturation to understand the true state of the system. A failed deployment step does not necessarily mean the service is unhealthy, just as a "successful" deployment can mask degraded performance. Always validate actual system behavior metrics before deciding on next steps.
Prepare for Known Failure Scenarios [Failure Catalog]¶
Maintain a living catalog of known failure scenarios—such as database migration timeouts, configuration mismatches, or resource exhaustion—and document their specific remediation strategies. By defining detection signals and response procedures for each pattern, you transform chaotic incidents into manageable scenarios with clear playbooks. This catalog must evolve with the code, where every team member shares responsibility for updating it as part of the incident resolution process, compounding organizational learning over time.
Stabilize Before You Investigate [Stabilization First] {1}¶
The primary objective during an incident is stabilization, not understanding. Restore a known healthy state immediately and reserve root-cause analysis for after the service is recovered. Users care about service restoration, not explanations, so curiosity must take a backseat to mitigation. Once the system is stable and users are protected, teams can safely investigate root causes in a controlled environment, strictly separating incident response from incident learning.
Rollback Is the Default Response [Rollback First] {1}¶
When deployment issues arise, rollback must be the immediate operational reflex. Do not attempt to debug forward, apply hotfixes, or patch issues directly in production, as these approaches exponentially increase risk and prolong downtime. Instead, revert to the last known good state instantly to restore stability. This principle prioritizes user impact over developer convenience: secure the platform first, then analyze and fix the problem in a lower environment.
Communicate Status Transparently [Incident Communication]¶
Transparent communication during a failure is a responsibility, not an option; therefore, all customer-impacting issues should be published. Update status pages immediately with honest assessments like "We are aware of an issue and actively working to restore service," even before the root cause is understood, as silence breeds speculation and distrust. Simultaneously, notify internal support teams to ensure they can provide accurate information to customers. Maintaining this transparency from the first alert through to the final resolution builds long-term credibility with your users.
Learn from Failure: Blameless Post-Mortems [Blameless Culture] {1}¶
Every deployment failure is a strategic learning opportunity, not a personnel failure. Conduct blameless post-mortems that focus rigidly on system weaknesses rather than individual mistakes. The document should detail the timeline, impact, and root cause (using techniques like the "Five Whys"), but most importantly, it must yield actionable improvements such as new automated tests, improved alerting, or architectural safeguards. A mature organization measures success by how effectively it adapts its processes to prevent the recurrence of the same error.
Related Topics
For broader incident management practices, including preparation, detection, and post-incident reviews, see the Incident Response section in the Security chapter.
Zero Downtime Challenges [Downtime Challenges]¶
Achieving zero downtime during deployments presents several significant challenges, particularly when dealing with stateful applications and long-running processes.
State: Applications that maintain state locally, such as session data, in-memory caching, or database connections, complicate the process. These instances cannot be easily replaced or scaled down without risking data loss or service disruption.
The most effective way to tackle this challenge is by transitioning from stateful to stateless instances, leveraging the principle of decoupling. This approach involves offloading state management to provider-managed services. For instance, session data can be migrated to managed Redis instances, while local caching should be replaced with distributed cache systems. Database connection pooling can be efficiently handled by services like Amazon RDS Proxy or Azure SQL Database connection pooling. Additionally, consider using object storage services for persistent data.
Long-Running Processes: Dealing with long-running processes during deployments requires careful planning to avoid interruptions and ensure continuity. One effective approach is to implement graceful shutdowns. This involves configuring the application to complete ongoing tasks before stopping, ensuring that no data is lost or processes are abruptly terminated.
Additionally, you can design your system to support process checkpointing, where the state of a long-running task is periodically saved. This allows the task to be paused and resumed seamlessly, even across deployments.
Minimizing Deployment Downtime: Blue-Green [Zero Downtime]¶
Achieving zero downtime during deployment requires careful orchestration to avoid service interruptions. The blue-green deployment method maintains two identical environments (side by side): the current production (blue) and the new version (green). Traffic is switched seamlessly from blue to green after verification.
Rolling deployments apply updates incrementally to different parts of the infrastructure. These strategies, combined with automated testing and monitoring, help achieve zero downtime, ensuring uninterrupted service during deployments.
Grace Periods: Provide a grace period during which both the old and new versions of a service can coexist. This allows dependent services to upgrade at their own pace without disrupting the stack.
Stable Interfaces: Define clear and stable interfaces (APIs) between services. Changes to these interfaces should be backward-compatible to avoid breaking dependent services.
API Versioning: If an interface change is necessary, use versioning (e.g., /v1/, /v2/) to allow the dependent service to continue using the old version until it is ready to upgrade. This approach provides time for the dependent service to adapt to the new interface.
Asynchronous Communication: Where possible, use asynchronous communication (e.g., message queues, event-driven architecture) to decouple services. This reduces tight coupling and allows services to evolve independently.
Feature Toggles: Implement feature toggles to control the rollout of new features or interface changes between services. This allows you to deploy changes to one service without immediately impacting the other.
https://reflectoring.io/blog/2022/2022-10-10-continuous-deployment-practices/






























































































