← Back to DevBytes

Flagger Canary Deployments: Complete Implementation Guide

Introduction to Flagger and Canary Deployments

Flagger is an open-source progressive delivery operator for Kubernetes, originally created by Weaveworks and now part of the Flux ecosystem. It automates canary deployments by gradually shifting traffic to a new version of your application while monitoring key metrics like request latency, error rates, and success rates. If the new version meets your defined quality gates, Flagger completes the rollout. If it fails, Flagger automatically rolls back to the stable version, minimizing user impact.

What Exactly Is a Canary Deployment?

A canary deployment is a technique where a new version of an application (the "canary") is deployed alongside the existing stable version. A small percentage of production traffic is routed to the canary, allowing you to test real-world behavior with a fraction of users. If the canary performs well, traffic increases incrementally until 100% of traffic reaches the new version. If problems arise, traffic is shifted back to the stable version immediately. Flagger automates this entire process using metrics from your service mesh or ingress controller.

Why Flagger Matters for Modern DevOps

Manual canary deployments require constant human monitoring, manual traffic shifting, and subjective decisions about when to promote or roll back. Flagger replaces this fragile process with automated, metric-driven decisions. Key benefits include:

How Flagger Works: Architecture Deep Dive

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Flagger operates as a Kubernetes controller. It watches for changes to a custom resource called a Canary resource. When a new container image is detected in the primary deployment, Flagger initiates the following workflow:

Key Custom Resources

Flagger introduces three essential custom resource definitions (CRDs):

Prerequisites for This Tutorial

Before you begin, ensure you have the following in place:

Step-by-Step Implementation Guide

Step 1: Install Prometheus (If Not Already Present)

Flagger relies on Prometheus metrics by default. Install the Prometheus community stack with Helm:

# Add the Prometheus community Helm repository
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# Install kube-prometheus-stack in the monitoring namespace
helm upgrade --install prometheus prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --wait

# Verify Prometheus is running
kubectl get pods -n monitoring
kubectl get svc -n monitoring

Step 2: Choose Your Service Mesh or Ingress Controller

For this guide, we'll use Istio, but Flagger supports many providers. Install Istio with the following:

# Download Istio
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.19.0 sh -
cd istio-1.19.0
export PATH=$PWD/bin:$PATH

# Install Istio with default profile
istioctl install --set profile=default -y

# Enable automatic sidecar injection in your namespace
kubectl label namespace default istio-injection=enabled

# Verify Istio components
kubectl get pods -n istio-system
kubectl get svc -n istio-system

Step 3: Install Flagger

Flagger is installed via Helm. Add the Flagger repository and install it, specifying your mesh provider:

# Add the Flagger Helm repository
helm repo add flagger https://flagger.app
helm repo update

# Install Flagger configured for Istio
helm upgrade --install flagger flagger/flagger \
  --namespace flagger-system \
  --create-namespace \
  --set meshProvider=istio \
  --set metricsServer=http://prometheus-kube-prometheus-prometheus.monitoring:9090 \
  --set selectorLabels="app.kubernetes.io/name" \
  --wait

# Verify Flagger is running
kubectl get pods -n flagger-system
kubectl get crd | grep flagger

The meshProvider parameter tells Flagger which traffic routing system to interact with. Available options include: istio, linkerd, nginx, contour, traefik, and gloo.

Step 4: Deploy a Sample Application with Canary Configuration

Create a namespace and deploy a sample application with both the primary deployment and a Canary resource. First, deploy the application itself:

# Create a namespace for the demo
kubectl create namespace canary-demo
kubectl label namespace canary-demo istio-injection=enabled

# Create the deployment and service
cat <

Step 5: Create the Flagger Canary Resource

The Canary resource is the heart of Flagger's progressive delivery. It defines the analysis interval, thresholds, and metrics that govern the canary process:

cat <

Let's break down the critical fields in this configuration:

  • targetRef: Points to the Kubernetes Deployment Flagger should watch for image updates
  • service.gateways: The Istio gateway that handles external traffic to this service
  • analysis.interval: How often Flagger checks metrics during the canary
  • analysis.threshold: The number of consecutive failed metric checks that trigger a rollback
  • analysis.maxWeight: The maximum percentage of traffic routed to the canary
  • analysis.stepWeights: The sequence of traffic percentages as the canary progresses
  • metrics: Prometheus queries that evaluate canary health. These are run against the canary pod and compared to the primary
  • webhooks: External hooks for pre-rollout validation, load testing, or post-rollout notifications

Step 6: Trigger a Canary Deployment

To trigger Flagger's canary process, simply update the container image of the watched deployment. Flagger detects the change automatically:

# Update the deployment image to trigger the canary
kubectl set image deployment/myapp myapp=nginx:1.26 -n canary-demo

# Watch Flagger create the canary resources
kubectl get canary -n canary-demo -w

# In another terminal, watch the deployments
kubectl get deployments -n canary-demo -w

# View detailed events from the Canary resource
kubectl describe canary myapp -n canary-demo

When you update the image, Flagger automatically:

  1. Creates a myapp-primary deployment pinned to the old image
  2. Creates a myapp-canary deployment with the new image
  3. Creates Istio VirtualService and DestinationRule resources for traffic splitting
  4. Begins shifting traffic incrementally according to stepWeights
  5. Runs metric analysis at each interval
  6. Either promotes the canary to primary or rolls back on failure

Step 7: Monitoring the Canary in Real Time

You can monitor the canary's progress using Flagger's built-in logging and the Canary resource status:

# Watch Flagger controller logs
kubectl logs -n flagger-system -l app.kubernetes.io/name=flagger -f

# Check the canary status with detailed conditions
kubectl get canary myapp -n canary-demo -o json | jq '.status'

# View the traffic split configuration
kubectl get virtualservices.networking.istio.io -n canary-demo -o yaml

# See the DestinationRule for traffic weights
kubectl get destinationrules.networking.istio.io -n canary-demo -o yaml

The canary status output shows conditions like:

{
  "conditions": [
    {
      "type": "Promoted",
      "status": "True",
      "lastTransitionTime": "2024-01-15T10:23:45Z"
    }
  ],
  "phase": "Succeeded",
  "canaryWeight": 0,
  "failedChecks": 0,
  "iterations": 4,
  "lastAppliedSpec": "...",
  "lastPromotedSpec": "...",
  "lastTransitionTime": "2024-01-15T10:23:45Z"
}

Step 8: Simulating a Failed Canary and Automatic Rollback

To see Flagger's rollback in action, deploy a deliberately broken version. Create a deployment that returns HTTP 500 errors:

# Deploy a broken version that will fail metric checks
cat <

As Flagger shifts traffic to the canary and detects failing metrics (error rates exceeding your threshold), you'll see the rollback happen automatically:

# Watch the rollback in real time
kubectl get canary myapp -n canary-demo -w

# Check failedChecks and phase
kubectl get canary myapp -n canary-demo -o json | jq '.status | {phase, failedChecks, canaryWeight}'

When the threshold of consecutive failures is reached, Flagger sets the canary weight back to 0%, deletes the canary deployment, and the primary deployment continues serving traffic with the original stable image.

Advanced Configuration Patterns

Using MetricTemplates for Reusable Metrics

MetricTemplates allow you to define metrics once and reference them across multiple Canary resources, reducing duplication:

cat <

The {{ namespace }} and {{ name }} template variables are automatically populated by Flagger with the canary's namespace and the deployment name.

Configuring Webhooks for Pre-Rollout Validation

Webhooks integrate external testing systems into the canary pipeline. Pre-rollout webhooks run before any traffic is shifted, providing a gate for integration tests:

# Example webhook configuration within a Canary resource
analysis:
  webhooks:
  - name: integration-tests
    type: pre-rollout
    url: http://test-runner.default:8080/run-tests
    timeout: 5m
    metadata:
      testSuite: full-integration
      targetEnv: canary
    headers:
      X-API-Key: "your-secret-key"
  - name: notify-slack
    type: confirm-promotion
    url: http://slack-bridge.default:8080/notify
    timeout: 30s
  - name: notify-rollback
    type: rollback
    url: http://slack-bridge.default:8080/rollback-alert
    timeout: 30s

Webhook types include:

  • pre-rollout: Runs before any traffic is shifted to the canary
  • rollout: Runs at each traffic weight step during the canary
  • confirm-promotion: Runs before the canary is fully promoted to primary
  • post-rollout: Runs after the canary has been promoted
  • rollback: Runs when a rollback is triggered
  • pre-rollout-helm: Special type for Helm release testing

Configuring Alert Notifications

Flagger can send alerts to Slack, Microsoft Teams, or Discord when canary events occur:

cat <

Integrating with Linkerd Instead of Istio

Flagger works seamlessly with Linkerd. The configuration differs primarily in the mesh provider setting and metric queries:

# Install Flagger for Linkerd
helm upgrade --install flagger flagger/flagger \
  --namespace flagger-system \
  --create-namespace \
  --set meshProvider=linkerd \
  --set metricsServer=http://prometheus-kube-prometheus-prometheus.monitoring:9090 \
  --wait

# Linkerd-specific metric queries in the Canary resource
metrics:
- name: request-success-rate
  thresholdRange:
    min: 99
  interval: 30s
  query: |
    sum(
      rate(
        response_total{
          namespace="canary-demo",
          deployment="myapp",
          classification!="success"
        }[1m]
      )
    ) / sum(
      rate(
        response_total{
          namespace="canary-demo",
          deployment="myapp"
        }[1m]
      )
    ) * 100
- name: request-duration
  thresholdRange:
    max: 500
  interval: 30s
  query: |
    histogram_quantile(0.99,
      sum(
        rate(
          response_latency_ms_bucket{
            namespace="canary-demo",
            deployment="myapp"
          }[1m]
        )
      ) by (le)
    )

NGINX Ingress Controller Integration

For environments without a service mesh, Flagger can use the NGINX ingress controller for traffic splitting:

# Install Flagger with NGINX support
helm upgrade --install flagger flagger/flagger \
  --namespace flagger-system \
  --create-namespace \
  --set meshProvider=nginx \
  --set ingressClass=nginx \
  --set metricsServer=http://prometheus-kube-prometheus-prometheus.monitoring:9090 \
  --wait

# Canary configuration for NGINX
spec:
  service:
    port: 80
    hosts:
    - myapp.example.com
  analysis:
    interval: 30s
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99
      interval: 30s
      query: |
        100 - sum(
          rate(
            nginx_ingress_controller_requests{
              namespace="canary-demo",
              service="myapp",
              status!~"2[0-9][0-9]"
            }[1m]
          )
        ) / sum(
          rate(
            nginx_ingress_controller_requests{
              namespace="canary-demo",
              service="myapp"
            }[1m]
          )
        ) * 100

Best Practices for Flagger Canary Deployments

1. Define Meaningful Metric Thresholds

Your metric thresholds should reflect real SLOs (Service Level Objectives). If your service targets 99.9% availability, set the success rate minimum to 99.9. If your P99 latency SLO is 200ms, set the request duration maximum to 200. Meaningless thresholds lead to false positives or, worse, promoting broken releases.

# Example of well-tuned thresholds aligned with SLOs
metrics:
- name: request-success-rate
  thresholdRange:
    min: 99.9  # Matches your SLO of 99.9% availability
  interval: 30s
- name: request-duration
  thresholdRange:
    max: 200   # Matches your P99 latency SLO of 200ms
  interval: 30s

2. Use StepWeights That Match Your Traffic Pattern

Aggressive step weights (like jumping directly to 50%) work for low-risk changes. For high-risk database migrations or API changes, use gradual steps:

# Conservative progression for high-risk changes
stepWeights: [1, 5, 10, 30, 70, 100]

# Moderate progression for normal releases
stepWeights: [5, 20, 50, 100]

# Quick progression for low-risk configuration changes
stepWeights: [10, 50, 100]

3. Combine Multiple Metrics for Robust Analysis

A single metric can miss problems. Combine success rate, latency, and resource utilization for a comprehensive health picture:

metrics:
- name: request-success-rate
  thresholdRange:
    min: 99
  interval: 30s
- name: request-duration-p99
  thresholdRange:
    max: 500
  interval: 30s
- name: cpu-usage
  thresholdRange:
    max: 80
  interval: 30s
  query: |
    avg(
      rate(
        container_cpu_usage_seconds_total{
          namespace="canary-demo",
          pod=~"myapp-canary-.*"
        }[1m]
      )
    ) * 100
- name: memory-usage
  thresholdRange:
    max: 1024
  interval: 30s
  query: |
    avg(
      container_memory_working_set_bytes{
        namespace="canary-demo",
        pod=~"myapp-canary-.*"
      }
    ) / 1024 / 1024

4. Implement Pre-Rollout Webhooks for Integration Testing

Run integration tests against the canary pod before any production traffic reaches it. This catches configuration errors, missing dependencies, and startup failures early:

webhooks:
- name: smoke-test
  type: pre-rollout
  url: http://smoke-tester.canary-demo:8080/test
  timeout: 3m
  metadata:
    testPlan: canary-smoke
    endpoint: http://myapp-canary.canary-demo:80
- name: migration-check
  type: pre-rollout
  url: http://db-migration-checker.canary-demo:8080/verify
  timeout: 5m

5. Set Appropriate Analysis Windows

The analysis interval and threshold together determine how long a canary must prove itself. For a service with 30s interval and threshold of 5, the minimum successful analysis period is 150 seconds (5 * 30s). Choose values that balance speed with safety:

# Fast canary for low-risk services (2.5 minutes minimum)
analysis:
  interval: 30s
  threshold: 5

# Extended analysis for critical services (10 minutes minimum)
analysis:
  interval: 60s
  threshold: 10

6. Namespace and Label Organization

Keep your Canary resources, MetricTemplates, and AlertProviders logically organized by namespace. Use consistent labels to filter Flagger's monitoring:

# Label your namespaces consistently
kubectl label namespace canary-demo \
  progressive-delivery=enabled \
  team=backend \
  environment=production

# Use selectorLabels in Flagger's Helm values
--set selectorLabels="app.kubernetes.io/name,app.kubernetes.io/instance"

7. GitOps Integration with Flux

Flagger is part of the Flux ecosystem. Store your Canary definitions in Git alongside your application manifests. Flux automatically reconciles changes, and Flagger picks up image updates from the Git repository:

# Example Flux HelmRelease for Flagger
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: flagger
  namespace: flagger-system
spec:
  interval: 5m
  chart:
    spec:
      chart: flagger
      version: 1.33.x
      sourceRef:
        kind: HelmRepository
        name: flagger
        namespace: flagger-system
  values:
    meshProvider: istio
    metricsServer: http://prometheus-kube-prometheus-prometheus.monitoring:9090

8. Monitor Flagger Itself

Flagger exports Prometheus metrics about its own operations. Set up alerts for Flagger failures to know when the canary system itself has issues:

# Sample Prometheus alert rule for Flagger
groups:
- name: flagger-alerts
  rules:
  - alert: FlaggerRolloutFailed
    expr: |
      increase(flagger_canary_rollout_failed_total[5m]) > 0
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "Flagger canary rollout failed"
      description: "Canary {{ $labels.name }} in namespace {{ $labels.namespace }} has failed"
  - alert: FlaggerControllerDown
    expr: |
      up{job="flagger"} == 0
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "Flagger controller is not running"

Troubleshooting Common Issues

Canary Stuck in Progress

If a canary remains in "Progressing" phase indefinitely, check:

# Check the Canary resource events
kubectl describe canary myapp -n canary-demo

# Verify Prometheus connectivity
kubectl logs -n flagger-system -l app.kubernetes.io/name=flagger | grep -i prometheus

# Check if the VirtualService was created
kubectl get virtualservices.networking.istio.io -n canary-demo

# Ensure the canary deployment exists and pods are running
kubectl get pods -n canary-demo -l app=myapp

Metric Queries Returning No Data

Test your Prometheus queries directly in the Prometheus UI before adding them to a Canary resource. Ensure namespace and service name filters match exactly:

# Port-forward Prometheus to test queries
kubectl port-forward -n monitoring svc/prometheus-kube-prometheus-prometheus 9090:9090

# In another terminal, test your query
curl -s 'http://localhost:9090/api/v1/query?query=sum(rate(istio_requests_total{destination_service_namespace="canary-demo"}[1m]))' | jq

Traffic Not Shifting

Verify that your service mesh is correctly configured and the gateway exists:

# For Istio, check the gateway
kubectl get gateway -n istio-system

# Verify sidecar injection
kubectl get pods -n canary-demo -o json | jq '.items[].spec.containers[].name' | grep istio-proxy

# Check DestinationRule weights
kubectl get destinationrules.networking.istio.io -n canary-demo -o yaml | grep -A 10 "weight"

Conclusion

Flagger transforms Kubernetes deployments from a binary pass/fail operation into a graduated, metric-driven progressive delivery pipeline. By integrating with your service mesh and monitoring stack, Flagger automates the most critical phase of software delivery—validating new versions against real traffic—while providing safety nets through automatic rollback. The combination of customizable metric thresholds, flexible traffic step weights, and webhook-driven testing gates gives teams confidence to deploy frequently without sacrificing reliability. Whether you use Istio, Linkerd, NGINX, or another provider, Flagger's consistent Canary resource model means you define your deployment strategy once and apply it across your entire service fleet. Start with conservative thresholds and step weights, build robust pre-rollout testing webhooks, and integrate Flagger into your GitOps workflow for a complete progressive delivery system that keeps your users safe while accelerating your release velocity.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles