TutorialsJun 5, 202615 min read

How to set up blue-green deployments with CircleCI

Roger Winter

Content Marketing Manager

How to set up blue-green deployments with CircleCI

A blue-green deployment runs two identical production environments and switches traffic between them when shipping a new version.

One environment is “active” and serves all traffic. The other sits idle. To deploy, the team pushes the new version to the idle environment, validates it, then switches traffic over. If something breaks, traffic flips back in seconds. No pods restart, no rolling update in reverse.

In a CI/CD workflow (where code changes are automatically built, tested, and deployed), blue-green separates the deploy step from the release step cleanly. CircleCI automates each stage: build the image, deploy to the idle environment, run validation tests, hold at an approval gate, and switch traffic only after a human confirms.

This tutorial guides you through setting up a blue-green deployment on Kubernetes, automated inside a CircleCI pipeline. It covers the dual-environment manifests, a preview Service for pre-switch validation, an approval-gated workflow, deploy tracking, instant rollback, and the database migration pattern that prevents the most common blue-green failure.

Prerequisites

Before starting, the team needs:

How blue-green deployments work in Kubernetes

Two Kubernetes Deployments exist side by side, one labeled version: blue, one labeled version: green. A Service routes traffic to whichever Deployment matches its selector. Only one is active at a time. The other runs idle, waiting for the next release.

Switching from blue to green means patching the Service selector from version: blue to version: green. This is a metadata change, not a pod operation. Traffic shifts immediately. No pods are created, terminated, or restarted.

Two Kubernetes architecture diagrams comparing blue-green deployment states. Before the switch, the Service selector targets `version: blue` and routes traffic to three blue pods running v1.0 while three green v2.0 pods sit idle. After the switch, the selector targets `version: green` and routes to the green pods while the blue pods stay on standby.

Before the switch, the team can validate the idle environment. A separate “preview” Service points permanently at the green Deployment, giving the pipeline an endpoint to run smoke tests, integration tests, or load tests against the new version. Because the green pods run on production infrastructure with access to the production database, tests here catch issues that staging environments miss.

Both deployments share the same database and external services. This is the source of most blue-green failures: a schema change that works with the green code but breaks the blue code makes rollback impossible. Step 5 covers the migration pattern that prevents this.

The trade-off: blue-green doubles compute during the deployment window because both environments run full replica sets simultaneously. For teams where that cost matters, rolling deployments update pods incrementally without a duplicate environment.

Step 1 — Build and containerize the sample application

The sample application is a Node.js HTTP server with no dependencies. It serves three endpoints:

  • GET / returns a styled HTML status page showing the running version, deployment slot (blue or green), pod hostname, and a timestamp.
  • GET /api returns the same information as JSON.
  • GET /health returns {"status":"ok"} for readiness and liveness probes.

The version comes from the APP_VERSION environment variable, and the slot comes from DEPLOY_SLOT. Both are set by the Kubernetes manifest at deploy time. Hitting GET /api on the blue and green environments returns different slot values, making the switch visible.

The full source is in the sample repository. The Dockerfile uses node:20-alpine and runs as a non-root user:

FROM node:20-alpine
WORKDIR /app
COPY package.json .
COPY index.js .
EXPOSE 3000
USER node
CMD ["node", "index.js"]

Build and test locally before pushing:

docker build -t blue-green-tutorial-app:local ./app
docker run -p 3000:3000 -e DEPLOY_SLOT=blue blue-green-tutorial-app:local

# In another terminal:
curl localhost:3000/api
# {"version":"1.0.0","slot":"blue","hostname":"...","timestamp":"..."}

curl localhost:3000/health
# {"status":"ok"}

Step 2 — Write the Kubernetes manifests

Blue-green on Kubernetes needs four manifests: two Deployments (one per environment) and two Services (production and preview). All four use envsubst placeholders that the CircleCI pipeline fills in at deploy time. envsubst replaces ${VARIABLE} references in a file with their environment variable values, so the pipeline can inject the image tag and Docker Hub username before applying the manifests.

Blue deployment

The blue Deployment in k8s/deployment-blue.yml runs the current production version. The version: blue label distinguishes its pods from green pods.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: blue-green-tutorial-app-blue
  namespace: default
  annotations:
    circleci.com/project-id: ${CIRCLE_PROJECT_ID}
    circleci.com/operation-timeout: 10m
  labels:
    app: blue-green-tutorial-app
    version: blue
    circleci.com/component-name: blue-green-tutorial-app
    circleci.com/version: ${IMAGE_TAG}
spec:
  replicas: 3
  selector:
    matchLabels:
      app: blue-green-tutorial-app
      version: blue
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: blue-green-tutorial-app
        version: blue
        circleci.com/component-name: blue-green-tutorial-app
        circleci.com/version: ${IMAGE_TAG}
    spec:
      containers:
        - name: blue-green-tutorial-app
          image: ${DOCKERHUB_USERNAME}/blue-green-tutorial-app:${IMAGE_TAG}
          ports:
            - containerPort: 3000
          env:
            - name: APP_VERSION
              value: "${IMAGE_TAG}"
            - name: DEPLOY_SLOT
              value: "blue"
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 5
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 15
            periodSeconds: 10

The key settings to note:

  • version: blue label. Appears in selector.matchLabels, the pod template, and the production Service’s selector. This is what isolates blue pods as a distinct set and lets the Service target them specifically.
  • maxUnavailable: 0 and maxSurge: 1. When the pipeline applies a new image to the green Deployment, no pods go offline until a replacement is ready, and only one extra pod runs at a time.
  • circleci.com/* labels and annotations. CircleCI’s deploy tracking reads these from running pods. They must not go in spec.selector.matchLabels, which Kubernetes treats as immutable. Review the component configuration docs for the full reference.

The green Deployment in k8s/deployment-green.yml is identical except all blue references become green: the metadata name, version label, and DEPLOY_SLOT value.

Production Service

The production Service in k8s/service.yml routes traffic to whichever environment is active. Initially, it selects version: blue:

apiVersion: v1
kind: Service
metadata:
  name: blue-green-tutorial-app
  namespace: default
spec:
  selector:
    app: blue-green-tutorial-app
    version: blue
  ports:
    - port: 80
      targetPort: 3000
  type: LoadBalancer

Switching traffic means patching this Service’s selector from version: blue to version: green. That single change redirects all production traffic to the green pods.

Preview Service

The preview Service in k8s/service-preview.yml always points to the green environment. It gives the pipeline an endpoint to validate the new version before the production switch:

apiVersion: v1
kind: Service
metadata:
  name: blue-green-tutorial-app-preview
  namespace: default
spec:
  selector:
    app: blue-green-tutorial-app
    version: green
  ports:
    - port: 80
      targetPort: 3000
  type: LoadBalancer

Create the cluster

This tutorial uses GKE for the cluster, but any Kubernetes cluster works. The manifests and pipeline are cloud-agnostic: EKS, AKS, DigitalOcean, or a local cluster like minikube or kind all run the same workflow.

For GKE, a single gcloud command creates the cluster:

gcloud container clusters create blue-green-tutorial \
  --zone=us-central1-a \
  --num-nodes=3 \
  --machine-type=e2-medium

Then configure kubectl to talk to the new cluster:

gcloud container clusters get-credentials blue-green-tutorial --zone=us-central1-a

get-credentials writes the cluster’s endpoint and credentials into ~/.kube/config and sets it as the active context. After this, every kubectl command targets the new cluster. No separate connection step needed.

Google Cloud’s GKE quickstart covers the full setup, including project setup, authentication, and billing. For other providers, follow their equivalent docs. The pattern is the same: provision a cluster, then point kubectl at it.

Verify the connection before moving on:

kubectl get nodes

Once nodes return, the rest of this tutorial is cloud-agnostic. Every command from here works the same regardless of where the cluster runs.

Initial setup

Before the first pipeline run, apply both Deployments and both Services manually:

export IMAGE_TAG=initial
export DOCKERHUB_USERNAME=<your-dockerhub-username>
export CIRCLE_PROJECT_ID=<your-circleci-project-id>

envsubst < k8s/deployment-blue.yml | kubectl apply -f -
envsubst < k8s/deployment-green.yml | kubectl apply -f -
kubectl apply -f k8s/service.yml
kubectl apply -f k8s/service-preview.yml

Both environments start with the same image. From here, the pipeline handles all updates to the green environment.

Step 3 — Create the CircleCI pipeline with approval gates

Connect the repository

In CircleCI, click Projects in the left sidebar, find the repository, and click Set Up Project. Select the Fastest option to use the existing .circleci/config.yml. For a full walkthrough, see Getting started with CircleCI.

Create the context

The pipeline needs secrets that shouldn’t live in the repository. CircleCI contexts store environment variables available to jobs at runtime. Create one named blue-green-tutorial:

  1. In CircleCI, go to Organization Settings > Contexts.
  2. Click Create Context and name it blue-green-tutorial.
  3. Add three environment variables:
Variable Value
DOCKERHUB_USERNAME Docker Hub username
DOCKERHUB_PASSWORD Docker Hub password or access token
KUBECONFIG_DATA Base64-encoded kubeconfig for the cluster

To generate KUBECONFIG_DATA for GKE:

gcloud container clusters get-credentials <cluster-name> --zone <zone> --project <project-id>
cat ~/.kube/config | base64 | tr -d '[:space:]'

For EKS, AKS, or other providers, configure kubectl access per the provider’s documentation, then encode the resulting kubeconfig the same way.

Pipeline config

The pipeline has five jobs in a linear workflow. This is where blue-green diverges from rolling: instead of deploying and releasing in one step, the pipeline separates them with a validation gate.

version: 2.1

commands:
  setup-kubectl:
    description: Install kubectl and configure cluster access
    steps:
      - run:
          name: Install kubectl
          command: |
            KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt)
            curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
            chmod +x kubectl
            sudo mv kubectl /usr/local/bin/kubectl
      - run:
          name: Configure kubeconfig
          command: |
            mkdir -p ~/.kube
            echo "$KUBECONFIG_DATA" | tr -d '[:space:]' | base64 --decode > ~/.kube/config
            chmod 600 ~/.kube/config

jobs:
  build-and-push:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: true
      - run:
          name: Build Docker image
          command: |
            docker build -t $DOCKERHUB_USERNAME/blue-green-tutorial-app:$CIRCLE_SHA1 ./app
      - run:
          name: Push to Docker Hub
          command: |
            echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
            docker push $DOCKERHUB_USERNAME/blue-green-tutorial-app:$CIRCLE_SHA1

  deploy-to-green:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - setup-kubectl
      - run:
          name: Install envsubst
          command: sudo apt-get update -qq && sudo apt-get install -y gettext-base
      - run:
          name: Deploy to green environment
          command: |
            export IMAGE_TAG=$CIRCLE_SHA1
            envsubst < k8s/deployment-green.yml | kubectl apply -f -
            kubectl apply -f k8s/service.yml
            kubectl apply -f k8s/service-preview.yml
      - run:
          name: Wait for green rollout
          command: kubectl rollout status deployment/blue-green-tutorial-app-green --timeout=5m

  validate-green:
    docker:
      - image: cimg/base:stable
    steps:
      - setup-kubectl
      - run:
          name: Get preview service endpoint
          command: |
            for i in $(seq 1 30); do
              PREVIEW_IP=$(kubectl get svc blue-green-tutorial-app-preview \
                -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
              if [ -n "$PREVIEW_IP" ]; then
                echo "Preview IP: $PREVIEW_IP"
                echo "export PREVIEW_IP=$PREVIEW_IP" >> "$BASH_ENV"
                break
              fi
              sleep 10
            done
            if [ -z "$PREVIEW_IP" ]; then
              echo "Preview service did not receive an external IP"
              exit 1
            fi
      - run:
          name: Smoke test - health check
          command: |
            curl -sf http://$PREVIEW_IP/health | grep -q '"status":"ok"'
      - run:
          name: Smoke test - version check
          command: |
            curl -sf http://$PREVIEW_IP/api | grep -q "$CIRCLE_SHA1"
      - run:
          name: Smoke test - slot check
          command: |
            curl -sf http://$PREVIEW_IP/api | grep -q '"slot":"green"'

  switch-traffic:
    docker:
      - image: cimg/base:stable
    steps:
      - setup-kubectl
      - run:
          name: Plan deployment
          command: |
            circleci run release plan "${CIRCLE_JOB}" \
              --environment-name="production" \
              --component-name="blue-green-tutorial-app" \
              --target-version="$CIRCLE_SHA1"
      - run:
          name: Switch production traffic to green
          command: |
            kubectl patch svc blue-green-tutorial-app \
              -p '{"spec":{"selector":{"version":"green"}}}'
      - run:
          name: Update deployment status to running
          command: circleci run release update "${CIRCLE_JOB}" --status=RUNNING
      - run:
          name: Log deploy marker
          command: |
            circleci run release log \
              --component-name=blue-green-tutorial-app \
              --environment-name=production \
              --target-version=$CIRCLE_SHA1
      - run:
          name: Update deployment status to success
          command: circleci run release update "${CIRCLE_JOB}" --status=SUCCESS
          when: on_success
      - run:
          name: Update deployment status to failed
          command: circleci run release update "${CIRCLE_JOB}" --status=FAILED
          when: on_fail

workflows:
  build-deploy:
    jobs:
      - build-and-push:
          context: blue-green-tutorial
      - deploy-to-green:
          requires:
            - build-and-push
          context: blue-green-tutorial
          filters:
            branches:
              only: main
      - validate-green:
          requires:
            - deploy-to-green
          context: blue-green-tutorial
      - hold-for-approval:
          type: approval
          requires:
            - validate-green
      - switch-traffic:
          requires:
            - hold-for-approval
          context: blue-green-tutorial

Walking through each job:

build-and-push builds the Docker image and tags it with the Git commit SHA ($CIRCLE_SHA1), so every image traces back to the exact commit that produced it.

deploy-to-green applies the green Deployment manifest with the new image tag and both Service manifests. Then it waits for the green rollout to complete. At this point, the new code is running in the green environment, but production traffic still goes to blue.

validate-green runs smoke tests against the preview Service. It checks that the health endpoint responds, that the API returns the expected version, and that the response comes from the green slot. If any test fails, the job fails and the approval gate never appears. Production stays on blue.

hold-for-approval is a CircleCI manual approval job. The pipeline pauses here. An engineer reviews the validation results, optionally runs additional manual checks against the preview endpoint, and clicks “Approve” in the CircleCI UI to proceed. This is the human checkpoint between deploying code and releasing it to users.

CircleCI pipeline view showing the build-deploy workflow paused at hold-for-approval. The job list shows build-and-push, deploy-to-green, and validate-green completed, with hold-for-approval awaiting input. A side panel offers Approve job and Cancel job buttons.

switch-traffic patches the production Service selector from version: blue to version: green. Traffic shifts immediately. This is the moment of release.

The job also writes deploy markers throughout. release plan registers the planned deploy before any work starts. After the switch, release update --status=RUNNING marks it active and release log records the event. A final release update closes the lifecycle with SUCCESS or FAILED. These markers feed CircleCI’s Deploys dashboard, where each release shows up as a timeline entry linked to the pipeline and commit.

The deploy chain runs only on main. Pull request builds stop after build-and-push without triggering a deploy.

CircleCI workflow view of a successfully completed Retrigger pipeline run. All five jobs in the build-deploy workflow (build-and-push, deploy-to-green, validate-green, hold-for-approval, and switch-traffic) show green checkmarks, with overall status Success.

Step 4 — Validate the green environment before switching

The validate-green job runs three checks against the preview Service endpoint:

  1. Health check: curl -sf http://$PREVIEW_IP/health returns {"status":"ok"}.
  2. Version check: The /api response contains the expected commit SHA.
  3. Slot check: The /api response confirms "slot":"green".

These three are the floor. A real production setup would add a smoke test suite covering critical API endpoints, a lightweight load test against the preview endpoint, and any database connectivity or migration checks the application needs. The validation job can run anything that makes HTTP requests, since the preview Service gives it a stable target.

Whatever the test suite, it runs against production infrastructure. Failures from connection pool limits, real data edge cases, or production-only config values surface here, before traffic shifts.

If validation fails, the pipeline stops. The approval gate never appears. Production traffic stays on blue. The team can inspect the green environment, check logs with kubectl logs -l version=green, and fix the issue before the next pipeline run.

Once the pipeline runs and switches traffic to green, the application serves a status page confirming the deployment:

Browser view of the deployed sample application showing a "Deployment successful" status card. The card displays version 901ebbb, slot green, the green pod name, and a deployment timestamp. The page is served from the GKE load balancer IP.

Step 5 — Handle database migrations safely

The expand-migrate-contract pattern lets schema changes ship through a blue-green pipeline without breaking either version of the app. It works in three phases:

Expand phase. Add new columns or tables alongside existing ones. Don’t remove anything. Deploy this schema change before the application change. The current blue code keeps working because nothing it depends on has been removed. The new green code works because the new structures exist.

Migrate phase. Backfill data from old structures into new ones. This can run as a background job or as a pipeline step after the schema expand is applied.

Contract phase. Once all traffic runs on the green version, and the team has confirmed the blue environment won’t be needed for rollback, remove the old columns. This is a separate deploy, run days or weeks later.

For example, renaming a column from user_name to username plays out like this:

Phase Schema state Blue code Green code
Before user_name exists Reads user_name
Expand Both columns exist, trigger syncs Reads user_name Reads username
Switch Both columns exist Still running, reads user_name Active, reads username
Contract Only username exists Scaled down Reads username

The constraint throughout is N-1 compatibility. The new app version must work with both the old schema and the new schema. The old app version must also work with the new schema. If either compatibility breaks, rollback fails.

Run the schema migration as a separate job in the pipeline, before the deploy-to-green job, so the database is ready before the new code hits it.

Step 6 — Set up instant rollback

Rollback patches the production Service selector back to version: blue. Traffic returns to the previous version immediately. No pods restart, no new ReplicaSet, no rolling update in reverse. The blue Deployment is still running with the previous image, so switching back is a network-level change that takes effect in seconds.

The rollback pipeline in .circleci/rollback.yml automates this from the CircleCI Deploys dashboard:

version: 2.1

commands:
  setup-kubectl:
    description: Install kubectl and configure cluster access
    steps:
      - run:
          name: Install kubectl
          command: |
            KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt)
            curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
            chmod +x kubectl && sudo mv kubectl /usr/local/bin/kubectl
      - run:
          name: Configure kubeconfig
          command: |
            mkdir -p ~/.kube
            echo "$KUBECONFIG_DATA" | tr -d '[:space:]' | base64 --decode > ~/.kube/config
            chmod 600 ~/.kube/config

jobs:
  rollback:
    docker:
      - image: cimg/base:stable
    environment:
      COMPONENT_NAME: << pipeline.deploy.component_name >>
      ENVIRONMENT_NAME: << pipeline.deploy.environment_name >>
      TARGET_VERSION: << pipeline.deploy.target_version >>
    steps:
      - setup-kubectl
      - run:
          name: Plan rollback release
          command: |
            circleci run release plan "${CIRCLE_JOB}" \
              --component-name=${COMPONENT_NAME} \
              --environment-name=${ENVIRONMENT_NAME} \
              --target-version=${TARGET_VERSION} \
              --rollback
      - run:
          name: Switch production traffic back to blue
          command: |
            kubectl patch svc blue-green-tutorial-app \
              -p '{"spec":{"selector":{"version":"blue"}}}'
      - run:
          name: Verify rollback
          command: |
            PROD_IP=$(kubectl get svc blue-green-tutorial-app \
              -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
            curl -sf http://$PROD_IP/api | grep -q '"slot":"blue"'
      - run:
          name: Update rollback status to SUCCESS
          command: circleci run release update "${CIRCLE_JOB}" --status=SUCCESS
          when: on_success
      - run:
          name: Update rollback status to FAILED
          command: circleci run release update "${CIRCLE_JOB}" --status=FAILED
          when: on_fail

  cancel-rollback:
    docker:
      - image: cimg/base:stable
    steps:
      - run:
          name: Update rollback status to CANCELED
          command: circleci run release update "${CIRCLE_JOB}" --status=CANCELED

workflows:
  rollback:
    jobs:
      - rollback:
          context: blue-green-tutorial
      - cancel-rollback:
          context: blue-green-tutorial
          requires:
            - rollback:
              - canceled

A few things to note:

  • pipeline.deploy.* parameters. Injected automatically when CircleCI triggers a rollback.
  • release plan --rollback. Distinguishes this from a regular deploy in the dashboard.
  • cancel-rollback job. Fires only on explicit cancellation, preventing stale “in progress” entries.

Connect the rollback pipeline

With the deploy markers from Step 3 committed to main, click Rollback > Create rollback pipeline from the project’s Overview page. The wizard handles four steps:

  1. GitHub App installation. The feature is GitHub-only; the wizard installs the App if needed.
  2. Pipeline definition. Select the project repository.
  3. Deploy markers. Since they’re already in the switch-traffic job, select I’ll make the config changes myself, then I’ve updated my config.
  4. Rollback config. Select I already have a rollback config to use .circleci/rollback.yml, then click Setup rollback pipeline.

Rollbacks now trigger from the project Overview page. The rollback pipeline documentation covers switching pipelines later via Project Settings > Deploys.

CircleCI "Run rollback" modal. The "Rolling back" section identifies the current blue-green-tutorial-app version, while "To the following deployment" shows the previous deployment SHA and its commit message. The dialog includes an optional rollback reason field and Cancel and Rollback buttons.

Step 7 — Monitor and confirm the release

After switching traffic, compare error rates and latency between the pre-switch and post-switch windows. A spike in errors immediately after the switch usually means the green code hit a production condition the validation tests didn’t cover.

Monitor database query patterns: new queries from the green code may create unexpected load. Connection pool usage and slow query logs are the first places to check.

Each switch is timestamped in CircleCI’s Deploys dashboard, so release events line up with metric changes in Prometheus, Datadog, or Grafana.

CircleCI Deploys dashboard for the blue-green-tutorial-app component. A timeline shows four production deployments with a mix of Logged and Success statuses. Below, two deployment rows display the same component, version SHA, environment, switch-traffic trigger, and publication times. One row shows Success and the other shows Logged.

Keep the blue environment running for a defined soak period after the switch: 30 minutes, an hour, whatever the team’s risk tolerance dictates. Only scale it down once the team is confident the release is stable and rollback won’t be needed. While the blue Deployment is still running, rollback takes seconds. After it’s scaled down, rolling back requires redeploying the previous image.

To check the current state of both environments:

kubectl get pods -l app=blue-green-tutorial-app -L version

This shows all pods with their version label, making it clear which environment is running what.

How CircleCI enables blue-green deployments

CircleCI’s role in this pipeline isn’t just running the steps. The approval job is what turns a blue-green deploy into a gated release: code reaches production infrastructure on every push to main, but traffic only shifts when an engineer clicks Approve in the CircleCI UI. That separation between deploy and release is what makes blue-green’s rollback fast. The previous version is still running, so reverting is a Service selector flip, not a redeploy. Deploy markers track each release in the Deploys dashboard, and the rollback pipeline turns reverts into a one-click action from that same dashboard.

Blue-green is one deployment strategy among many. Other deployment strategies make different trade-offs around speed, risk, and infrastructure cost. The same CircleCI primitives (approval gates, deploy markers, rollback pipelines) apply across all of them.

When to use blue-green (and when not to)

Blue-green makes sense when:

  • Instant rollback is a hard requirement (financial services, e-commerce checkout, healthcare).
  • The team can afford double the compute cost during the deployment window.
  • Pre-switch validation in a production environment matters.
  • Releases are infrequent and high-stakes.

Blue-green is the wrong choice when:

  • Infrastructure budget is tight (canary uses a fraction of the resources).
  • The team deploys many times per day (two-environment overhead doesn’t pay off at high frequency).
  • Percentage-based traffic shifting and metric-driven promotion is needed (that’s canary territory).

For a comparison of strategies, see rolling deployments on Kubernetes.

Conclusion

This tutorial built a complete blue-green pipeline on Kubernetes: deploy to a parallel environment, validate against production infrastructure, hold for human approval, switch traffic, and roll back from the dashboard if something breaks. The whole pipeline lives as YAML in the repository, runs on CircleCI’s free tier, and works against GKE, EKS, AKS, or any self-managed Kubernetes cluster.

To start building, sign up for a free CircleCI account and adapt the pipeline config to your own project.