<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Prabhav Mishra</title>
    <description>The latest articles on DEV Community by Prabhav Mishra (@prabhav_mishra_4f37d87a59).</description>
    <link>https://dev.to/prabhav_mishra_4f37d87a59</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4236551%2F15e1434b-36b0-4982-bdb9-4c7da6cc736f.jpeg</url>
      <title>DEV Community: Prabhav Mishra</title>
      <link>https://dev.to/prabhav_mishra_4f37d87a59</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prabhav_mishra_4f37d87a59"/>
    <language>en</language>
    <item>
      <title>Building a Canary Deployment Pipeline from Scratch: A Practical Guide</title>
      <dc:creator>Prabhav Mishra</dc:creator>
      <pubDate>Tue, 10 Nov 2026 07:59:07 +0000</pubDate>
      <link>https://dev.to/prabhav_mishra_4f37d87a59/building-a-canary-deployment-pipeline-from-scratch-a-practical-guide-4am7</link>
      <guid>https://dev.to/prabhav_mishra_4f37d87a59/building-a-canary-deployment-pipeline-from-scratch-a-practical-guide-4am7</guid>
      <description>&lt;p&gt;&lt;em&gt;How to release changes gradually, observe their impact, and reduce the&lt;br&gt;
risk of production deployments.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A deployment can succeed technically and still cause a production&lt;br&gt;
incident. A new version may start without errors, pass its health&lt;br&gt;
checks, and then fail under real traffic because of an unexpected&lt;br&gt;
workload, dependency, or configuration difference.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;canary deployment&lt;/strong&gt; reduces this risk by exposing a new version to a&lt;br&gt;
small portion of production traffic first. You observe its behaviour,&lt;br&gt;
compare it with the stable version, and increase traffic only when the&lt;br&gt;
evidence looks healthy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The examples below explain the workflow. Adapt the manifests,&lt;br&gt;
annotations, health checks, and rollout commands to your Kubernetes&lt;br&gt;
distribution and ingress controller before using them in production.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Where does the term "canary" come from?
&lt;/h2&gt;

&lt;p&gt;The term is inspired by the historical use of canaries in coal mines.&lt;br&gt;
Miners used the birds as an early-warning indicator for dangerous gases.&lt;br&gt;
This gave rise to the expression &lt;strong&gt;"canary in a coal mine"&lt;/strong&gt;---an early&lt;br&gt;
signal that something may be wrong.&lt;/p&gt;

&lt;p&gt;Software deployments use the same idea. Instead of exposing every user&lt;br&gt;
to a new release immediately, we expose a small portion of traffic to it&lt;br&gt;
first. If the new version shows elevated errors or latency, we can stop&lt;br&gt;
the rollout before the issue affects everyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle is simple: detect problems early, limit their impact,&lt;br&gt;
and expand only when the release is behaving as expected.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. What is a canary deployment?
&lt;/h2&gt;

&lt;p&gt;A canary deployment runs the new application version alongside the&lt;br&gt;
current stable version. Initially, only a small percentage of requests&lt;br&gt;
are routed to the canary.&lt;/p&gt;

&lt;p&gt;A typical rollout looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Version &lt;code&gt;v1&lt;/code&gt; continues serving most production traffic.&lt;/li&gt;
&lt;li&gt; Version &lt;code&gt;v2&lt;/code&gt; is deployed as the canary.&lt;/li&gt;
&lt;li&gt; A small percentage of traffic is routed to &lt;code&gt;v2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Metrics and logs are monitored for a defined observation period.&lt;/li&gt;
&lt;li&gt; If the canary is healthy, traffic is increased gradually.&lt;/li&gt;
&lt;li&gt; If it is unhealthy, traffic is returned to &lt;code&gt;v1&lt;/code&gt; and the canary is
rolled back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This differs from a rolling deployment, where Kubernetes gradually&lt;br&gt;
replaces old pods with new ones but does not, by itself, provide&lt;br&gt;
explicit traffic-percentage analysis and promotion.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why use canary deployments?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Reduced blast radius:&lt;/strong&gt; only a subset of requests initially
reaches the new version.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Production validation:&lt;/strong&gt; the release is tested against real
traffic and dependencies.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Controlled rollout:&lt;/strong&gt; promotion happens in measured stages.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Faster recovery:&lt;/strong&gt; traffic can be shifted back if the canary
regresses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Canary releases do not eliminate deployment risk. They make it easier to&lt;br&gt;
detect and contain problems before a full rollout.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. High-level architecture
&lt;/h2&gt;

&lt;p&gt;A basic canary pipeline contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;CI pipeline:&lt;/strong&gt; builds, tests, and publishes an immutable
application image.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Container registry:&lt;/strong&gt; stores the versioned image.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Kubernetes:&lt;/strong&gt; runs stable and canary workloads.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Ingress or traffic-management layer:&lt;/strong&gt; splits requests between
versions.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Monitoring:&lt;/strong&gt; compares health and performance.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;CD pipeline:&lt;/strong&gt; promotes the canary or rolls it back based on
checks and approval rules.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code change
    |
    v
Build and test
    |
    v
Publish immutable image
    |
    v
Deploy canary to Kubernetes
    |
    v
Route a small share of traffic to canary
    |
    v
Observe errors, latency and resource health
    |
    +---- Healthy? ---- Yes ----&amp;gt; Increase traffic
    |                                  |
    |                                  v
    |                           Promote new version
    |
    +---- No --------------------&amp;gt; Roll back traffic
                                       |
                                       v
                                Keep stable version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  3. Deploy stable and canary versions
&lt;/h2&gt;

&lt;p&gt;One straightforward approach is to use two Kubernetes Deployments and&lt;br&gt;
two Services. The stable Deployment runs the current release; the canary&lt;br&gt;
Deployment runs the candidate release.&lt;/p&gt;
&lt;h3&gt;
  
  
  Stable Deployment
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-stable&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sample-app&lt;/span&gt;
    &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sample-app&lt;/span&gt;
      &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sample-app&lt;/span&gt;
        &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example-registry/sample-app:v1&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
          &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/health&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Canary Deployment
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-canary&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sample-app&lt;/span&gt;
    &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;canary&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sample-app&lt;/span&gt;
      &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;canary&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sample-app&lt;/span&gt;
        &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;canary&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example-registry/sample-app:v2&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
          &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/health&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The labels distinguish the versions. Create a Service for each version&lt;br&gt;
so the ingress or traffic-management layer can route to them&lt;br&gt;
independently.&lt;/p&gt;
&lt;h3&gt;
  
  
  Stable Service
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-stable&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sample-app&lt;/span&gt;
    &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Canary Service
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-canary&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sample-app&lt;/span&gt;
    &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;canary&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Split traffic between stable and canary
&lt;/h2&gt;

&lt;p&gt;Traffic splitting depends on the ingress controller or service mesh.&lt;br&gt;
Some controllers support weighted routing; others use&lt;br&gt;
controller-specific annotations or custom resources.&lt;/p&gt;

&lt;p&gt;For example, an NGINX Ingress Controller configuration may use a stable&lt;br&gt;
Ingress and a canary Ingress. The following is an illustrative&lt;br&gt;
controller-specific example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-canary&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/canary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/canary-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"10"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ingressClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.example.com&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
            &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
            &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-canary&lt;/span&gt;
                &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a 10% canary weight, the controller is configured to send&lt;br&gt;
approximately 10% of eligible traffic to the canary and the rest to the&lt;br&gt;
stable backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Canary annotations and their behaviour vary by ingress&lt;br&gt;
controller and version. Check the documentation for the controller you&lt;br&gt;
use. For more advanced rollouts, tools such as Argo Rollouts or service&lt;br&gt;
meshes provide richer traffic management and analysis.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Build the deployment pipeline
&lt;/h2&gt;

&lt;p&gt;A useful pipeline should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Build and test the application.&lt;/li&gt;
&lt;li&gt; Publish an immutable image, ideally tagged with a commit SHA or
release identifier.&lt;/li&gt;
&lt;li&gt; Deploy the canary.&lt;/li&gt;
&lt;li&gt; Wait for Kubernetes readiness.&lt;/li&gt;
&lt;li&gt; Route a small percentage of traffic to the canary.&lt;/li&gt;
&lt;li&gt; Observe application and infrastructure metrics.&lt;/li&gt;
&lt;li&gt; Promote the release or roll it back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a simplified Bash example showing the deployment steps. In a&lt;br&gt;
real CI/CD system, credentials should come from the pipeline's secret&lt;br&gt;
store or workload identity---not from hard-coded values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; -euo pipefail

&lt;span class="nv"&gt;NAMESPACE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt;
&lt;span class="nv"&gt;IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"example-registry/sample-app:${GIT_COMMIT_SHA}"&lt;/span&gt;
&lt;span class="nv"&gt;CANARY_DEPLOYMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app-canary"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deploying canary image: ${IMAGE}"&lt;/span&gt;

kubectl &lt;span class="nb"&gt;set&lt;/span&gt; image deployment/&lt;span class="nv"&gt;${CANARY_DEPLOYMENT}&lt;/span&gt; \
  &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"${IMAGE}"&lt;/span&gt; \
  -n &lt;span class="s2"&gt;"${NAMESPACE}"&lt;/span&gt;

kubectl rollout status deployment/&lt;span class="nv"&gt;${CANARY_DEPLOYMENT}&lt;/span&gt; \
  -n &lt;span class="s2"&gt;"${NAMESPACE}"&lt;/span&gt; \
  --timeout=180s

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Canary pods are ready."&lt;/span&gt;

&lt;span class="c"&gt;# Configure the initial traffic weight using the mechanism&lt;/span&gt;
&lt;span class="c"&gt;# supported by your ingress controller or rollout platform.&lt;/span&gt;
kubectl annotate ingress app-canary \
  nginx.ingress.kubernetes.io/canary-weight=&lt;span class="s2"&gt;"10"&lt;/span&gt; \
  --overwrite \
  -n &lt;span class="s2"&gt;"${NAMESPACE}"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Canary traffic configured. Start the observation checks."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script deliberately stops after configuring the initial traffic&lt;br&gt;
weight. Monitoring and promotion should be explicit pipeline stages; a&lt;br&gt;
successful &lt;code&gt;kubectl&lt;/code&gt; command does not prove that a release is healthy.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Monitor the right signals
&lt;/h2&gt;

&lt;p&gt;A canary is only useful if you can tell whether it is performing worse&lt;br&gt;
than the stable release.&lt;/p&gt;

&lt;p&gt;Compare stable and canary over the same time window and, where possible,&lt;br&gt;
across comparable traffic. Relative comparison can reveal regressions&lt;br&gt;
that absolute thresholds miss.&lt;/p&gt;

&lt;p&gt;For example, a pipeline might require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Canary 5xx rate to remain below an agreed threshold.&lt;/li&gt;
&lt;li&gt;  Canary p95 latency not to regress beyond an agreed percentage
against stable.&lt;/li&gt;
&lt;li&gt;  No repeated pod restarts or readiness failures.&lt;/li&gt;
&lt;li&gt;  A minimum request volume before making a promotion decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose thresholds based on the service's baseline, SLOs, traffic volume,&lt;br&gt;
and business impact. Avoid promoting a canary based on a tiny sample of&lt;br&gt;
requests.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Gradually increase traffic
&lt;/h2&gt;

&lt;p&gt;Once the canary passes its initial checks, increase traffic in stages.&lt;br&gt;
Monitor the following signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP 5xx error rate:&lt;/strong&gt; Check whether server errors increase compared with the stable version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request latency:&lt;/strong&gt; Monitor p95 and p99 latency for regressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Availability:&lt;/strong&gt; Track failed health checks and successful request rates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU and memory:&lt;/strong&gt; Look for unexpected resource growth or throttling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pod health:&lt;/strong&gt; Monitor restarts, failed readiness probes, and crash loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency errors:&lt;/strong&gt; Watch for database, cache, and downstream-service failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare stable and canary metrics over the same time window and, where possible, across comparable traffic.&lt;/p&gt;

&lt;p&gt;For example, a pipeline might require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Canary 5xx error rate to remain below an agreed threshold.&lt;/li&gt;
&lt;li&gt;Canary p95 latency not to regress beyond an agreed percentage against stable.&lt;/li&gt;
&lt;li&gt;No repeated pod restarts or readiness failures.&lt;/li&gt;
&lt;li&gt;A minimum request volume before making a promotion decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose thresholds based on your service's baseline, SLOs, traffic volume, and business impact. Avoid promoting a canary based on a tiny sample of requests.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Gradually increase traffic
&lt;/h2&gt;

&lt;p&gt;Once the canary passes its initial checks, increase traffic in controlled stages.&lt;/p&gt;

&lt;p&gt;A typical rollout might look like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;10% traffic — Initial validation:&lt;/strong&gt; Route a small portion of production traffic to the canary and verify basic application behaviour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;25% traffic — Early expansion:&lt;/strong&gt; Increase exposure after the initial metrics look healthy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;50% traffic — Larger workload validation:&lt;/strong&gt; Observe the canary under a more representative production load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100% traffic — Full promotion:&lt;/strong&gt; Complete the rollout after all validation checks pass.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At every stage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow a sufficient observation period.&lt;/li&gt;
&lt;li&gt;Compare canary metrics against the stable version.&lt;/li&gt;
&lt;li&gt;Check error rates, latency, and pod health.&lt;/li&gt;
&lt;li&gt;Stop the rollout if any critical threshold is breached.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not increase traffic just because a timer has elapsed. Require enough representative traffic and healthy metrics to support the decision.&lt;/p&gt;

&lt;p&gt;Each stage should have an observation period and a clear pass/fail&lt;br&gt;
decision. The time required depends on traffic volume, service&lt;br&gt;
criticality, and how quickly meaningful signals become available.&lt;/p&gt;

&lt;p&gt;Do not increase traffic just because a timer has elapsed. Require enough&lt;br&gt;
representative traffic and healthy metrics to support the decision.&lt;/p&gt;
&lt;h2&gt;
  
  
  8. Roll back when the canary is unhealthy
&lt;/h2&gt;

&lt;p&gt;If the canary starts returning errors or shows a significant latency&lt;br&gt;
regression, stop sending traffic to it first. Then investigate and roll&lt;br&gt;
back the candidate workload.&lt;/p&gt;

&lt;p&gt;For the illustrative NGINX Ingress configuration above, traffic can be&lt;br&gt;
returned to stable by setting the canary weight to zero:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl annotate ingress app-canary \
  nginx.ingress.kubernetes.io/canary-weight=&lt;span class="s2"&gt;"0"&lt;/span&gt; \
  --overwrite \
  -n production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then roll back the canary Deployment if needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl rollout undo deployment/app-canary -n production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact rollback steps depend on your traffic-management setup. In a&lt;br&gt;
production pipeline, make rollback an explicit, tested operation and&lt;br&gt;
verify that traffic has actually returned to stable.&lt;/p&gt;

&lt;p&gt;Also consider database and API compatibility. Rolling back application&lt;br&gt;
pods may not reverse a destructive database migration or an incompatible&lt;br&gt;
change to a shared dependency. Prefer backward-compatible,&lt;br&gt;
expand-and-contract database migrations during progressive delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Best practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Use immutable image tags:&lt;/strong&gt; deploy a specific build identifier
rather than relying on a mutable tag such as &lt;code&gt;latest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Keep stable and canary observable separately:&lt;/strong&gt; label metrics and
logs with the release or track.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Define promotion criteria in advance:&lt;/strong&gt; use measurable thresholds
instead of subjective decisions.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Test rollback:&lt;/strong&gt; verify that traffic returns to stable and the
application remains healthy.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Account for stateful dependencies:&lt;/strong&gt; database schema changes,
queues, caches, and background workers can make rollback more
complex.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Use progressive traffic steps:&lt;/strong&gt; choose stages and observation
windows appropriate to the service.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Protect the pipeline:&lt;/strong&gt; use least-privilege access, secret
management, approvals where needed, and auditable deployment
records.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Automate carefully:&lt;/strong&gt; automatic promotion is useful only when
metrics, thresholds, and rollback behaviour are reliable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Final thoughts
&lt;/h2&gt;

&lt;p&gt;Canary deployments turn a production release from a single high-impact&lt;br&gt;
event into a sequence of smaller, observable decisions.&lt;/p&gt;

&lt;p&gt;The important part is not simply running two versions of an application.&lt;br&gt;
It is connecting deployment automation, traffic management, monitoring,&lt;br&gt;
and rollback into one controlled process.&lt;/p&gt;

&lt;p&gt;Start with a small traffic percentage, define what healthy looks like,&lt;br&gt;
and make every promotion decision based on evidence. That approach helps&lt;br&gt;
teams release more confidently while limiting the impact of unexpected&lt;br&gt;
issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://sre.google/workbook/canarying-releases/" rel="noopener noreferrer"&gt;Google SRE Workbook: Canarying
Releases&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://martinfowler.com/bliki/CanaryRelease.html" rel="noopener noreferrer"&gt;Martin Fowler: Canary
Release&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://kubernetes.io/docs/home/" rel="noopener noreferrer"&gt;Kubernetes Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://kubernetes.github.io/ingress-nginx/" rel="noopener noreferrer"&gt;NGINX Ingress Controller
Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;If you found this useful, share your thoughts or describe how your team&lt;br&gt;
handles progressive delivery. What signals do you use to decide whether&lt;br&gt;
a canary is safe to promote?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
