CI/CD Deployment Strategies

Decoupling deployments from releases using blue-green, canary, rolling updates, and expand-and-contract database migration patterns.

IntermediateInfrastructureChapter: Infrastructure12 min read

Releasing vs Deploying Code

In modern infrastructure engineering, developers must draw a clear distinction between deploying software and releasing features:

  • Continuous Integration (CI): The automated practice of merging code changes into a shared repository, running compilation steps, checking code style, and running test suites to catch errors early.
  • Continuous Delivery (CD): Automating the build and release pipeline so that the codebase is always in a deployable state. Deployments to production may still require manual approval.
  • Continuous Deployment: Automating the entire pipeline. Every change that passes automated tests is deployed to production without human intervention.
  • Deployment: The physical installation and execution of a new version of an application on host servers or containers. A deployed application runs in production but does not have to be active for users.
  • Release: The activation of a new feature or version for user traffic. This is typically managed via routing tables, load balancers, or application-level feature flags.

By separating deployment from release, teams can verify new software versions in production using test data before directing real user traffic to them.


Zero-Downtime Deployment Patterns

To update services without dropping user requests, modern pipelines use zero-downtime deployment patterns:

Blue-Green Deployments

This approach runs two identical production environments side-by-side:

  • Blue represents the active, live environment receiving traffic.
  • Green is the idle environment where the new code version is deployed.

Once the green environment is successfully deployed and passes sanity checks, the load balancer or router switches user traffic from Blue to Green. If a problem occurs, reverting is as simple as switching the router back to Blue. The primary downside is cost, as this requires doubling server capacity during deployment.

Canary Deployments

Instead of switching all traffic at once, a canary deployment routes traffic to the new version gradually:

  • The new version is deployed to a small subset of instances (the canaries).
  • The router directs a small percentage of user traffic (such as 1% or 5%) to these instances.
  • Automated systems monitor error rates, latency, and system load.
  • If metrics remain healthy, traffic is incremented (to 10%, 50%, then 100%). If anomalies are detected, the router immediately stops sending traffic to the canary nodes.

Kubernetes Rolling Updates

Kubernetes automates deployments using a rolling update strategy. Rather than shutting down all containers and starting new ones (which causes downtime), Kubernetes replaces instances of the old container with the new version incrementally.

Two main parameters control this pacing:

  • maxSurge: The maximum number of containers that can be created over the desired replica count during the update.
  • maxUnavailable: The maximum number of containers that can be offline relative to the target replica count.

Setting maxUnavailable: 0 ensures the cluster retains full capacity throughout the deployment. Crucially, Kubernetes relies on readinessProbes to determine when a new container is ready to accept traffic. If a container fails its readiness check, Kubernetes stops routing traffic to it and halts the rollout, preventing a bad build from taking down the cluster.


Database Schema Migrations: Expand-and-Contract

While application servers are stateless and easy to replace, databases store state. If an application update requires changing a database schema, deploying the new code and updating the schema simultaneously can cause crashes. Old application instances still running during a rolling update will fail when columns they expect are renamed or dropped.

To avoid this, teams use the Expand-and-Contract (or Parallel Run) migration pattern:

Expand-and-Contract Database Migrations State A: Baseline App V1 runs Uses old_column State B: Expand Add new_column Sync writes to both State C: Rollout App V2 fully active Reads new_column State D: Contract Drop old_column Clean up complete Migration Steps: * Step 1: Add new column, deploy code that writes to both but reads from old (Expand). * Step 2: Backfill historical data in background from old column to new column. * Step 3: Deploy code that reads and writes exclusively to the new column. * Step 4: Drop the old column and delete the sync trigger (Contract).

By splitting the change into multiple small, backwards-compatible deployments, the application remains fully functional at every point of the update cycle.


Pipeline Feedback Loops

Deploying software safely requires automated monitoring and feedback loops. A modern continuous deployment pipeline is integrated directly into Application Performance Monitoring (APM) tools.

During a deployment rollout (especially during Rolling or Canary phases), the deployment controller listens to performance APIs:

  • Error Rates: A sudden spike in HTTP 5xx responses triggers an automatic rollback.
  • Latency Spikes: If database queries or page response times exceed set thresholds, the deployment is aborted.
  • Host Health: High memory utilization or CPU spikes on new containers trigger an immediate revert.

By coupling the deployment system directly to observability metrics, the loop is closed: bad code is caught, isolated, and rolled back without requiring manual intervention from engineers.


Further Reading

Prerequisites

Code Examples

Core Literature References

Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation

by Jez Humble and David Farley — Chapter 10: Deploying and Releasing Applications, pp. 257-285

View source