Production-ready guide covering cloud-native blue-green deployment pipeline with implementation patterns, code examples, and anti-patterns for enterprise engineering teams.
Blue-green deployment pipelines exist to move traffic between two fully provisioned, identical environments—one “blue” (current) and one “green” (candidate)—without taking services offline. They matter when a system cannot tolerate even brief downtime, when rollback must be instantaneous, or when compliance requires that the previous version remains runnable until the new one is proven. The pipeline described here uses standard cloud-native tooling: container images, a Git-based source of truth, and a runtime that supports declarative workload descriptions. Every step is scripted so that a new version is built, deployed to the idle half, health-checked, and then switched into production with a single atomic operation. If anything fails, the selector is flipped back and the previous half continues serving traffic.
The pipeline begins in a CI system that watches a repository. On a merge to main or a tagged release, the pipeline builds a container image, pushes it to a registry, and enqueues two deployment manifests—one targeting the blue namespace, one targeting green. The manifests are otherwise identical except for the imageTag (or imagePullPolicy) and a namespace label. A typical build command looks like:
docker build -t myregistry/myapp:$(git rev-parse --short HEAD) .
docker push myregistry/myapp:$(git rev-parse --short HEAD)
The CI then renders two YAML manifests using a minimal templating step (e.g., envsubst or a sed pipeline). A sample rendered manifest fragment is:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-green
namespace: myapp-green
spec:
replicas: 3
selector:
matchLabels:
app: myapp
env: green
template:
metadata:
labels:
app: myapp
env: green
spec:
containers:
- name: myapp
image: myregistry/myapp:$(git rev-parse --short HEAD)
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myapp-secrets
key: database-url
No custom controller or vendor-specific hook is required. The pipeline relies only on the runtime’s native ability to distinguish workloads by label and namespace.
With the green manifest rendered, the next goal is to create or update the green environment so it mirrors the blue one exactly, then swap the image tag. In a Kubernetes-like runtime, this is done with kubectl:
kubectl apply -f manifests/green.yaml
kubectl apply -f manifests/blue.yaml # ensures blue is also present; may already exist
If the green namespace does not yet exist, a single command creates it:
kubectl create namespace myapp-green
The deployment is then triggered:
kubectl set env deployment/myapp-green IMAGE_TAG=$(git rev-parse --short HEAD)
The kubectl set env command is a real flagged operation; it patches the deployment’s .spec.template.spec.containers[0].image field and schedules a rollout. To verify the image was accepted:
kubectl get pods -l app=myapp,env=green -o wide
Expected output includes STATUS=Running and READY=1/3 once the image layers have been pulled and the container started. If the image name is misspelled or the tag does not exist in the registry, the runtime returns:
Error: failed to pull image "myregistry/myapp:abc123": do not know any tag
At this point the green half is stuck in ImagePullBackOff. The blue half remains untouched because the selector still points to the blue namespace’s deployment.
Once the green pods report Ready, the pipeline must route incoming requests away from blue and toward green. The exact mechanism depends on the runtime’s networking layer, but the pattern is always the same: change the label selector that the service or ingress uses, or swap the endpoints list. In a typical Kubernetes setup with a ClusterIP service and an ingress controller that respects label selectors, the traffic shift is a single kubectl patch:
kubectl patch service myapp -p '{"spec":{"selector":{"app":"myapp","env":"green"}}}'
The ingress controller, watching the service’s selector change, begins sending new connections to green pods immediately. Existing connections continue on blue until their sessions terminate or a configured idle timeout expires.
If the runtime uses an ingress resource with backend.service.name references rather than label selectors, the patch targets the ingress instead:
kubectl patch ingress myapp -p '{"spec":{"rules":[{"host":"myapp.example.com","http":{"paths":[{"pathType":"Prefix","path":"\/","backend":{"service":{"name":"myapp-green","port":{"number":8080}}}}]}}]}'
After the patch, the pipeline pauses to confirm that the new endpoints are healthy. A watch loop is common:
kubectl get endpoints myapp -w
Output progresses from blue-only addresses to include green pod IPs, then stabilizes with all addresses belonging to the green deployment.
After a configurable observation window—often defined by a health-check metric or a manual approval—the pipeline performs the final cutover. The most atomic way is to reverse the selector on the blue service, effectively “decommissioning” it:
kubectl patch service myapp -p '{"spec":{"selector":{"app":"myapp","env":"blue"}}}'
Because the selector now matches zero pods, the service’s endpoints slice becomes empty, and traffic (if the ingress continues to reference the service by name rather than selector) stops reaching blue. If the ingress uses a label selector, the same kubectl patch ingress command from the previous section is re-applied with the green selector.
At this point the green deployment is the sole entry point. The pipeline records the release version in a changelog or deployment record, then proceeds to teardown or archival of the blue resources. A common cleanup command is:
kubectl delete deployment myapp-blue -n myapp-blue
If the organization retains blue for rollback purposes, the deployment is left running but unselected; the namespace may be kept alive with a kubectl annotate namespace myapp-blue closed-on=$(date +%s) marker for later reference.
Rollback in a blue-green pipeline is simply a selector flip back to the previously verified half. If the green deployment exhibits failures after cutover—such as CrashLoopBackOff, failing health checks, or database migration errors—the pipeline reverts the service selector:
kubectl patch service myapp -p '{"spec":{"selector":{"app":"myapp","env":"blue"}}}'
Because blue was never scaled down, its pods continue running, and traffic resumes immediately. No image pull or migration is required; the runtime simply routes again to the known-good version.
Silent failure modes that often catch teams off guard include:
PersistentVolumeClaims, ConfigMaps, or Secrets left in the green namespace. A cleanup script run as a post-deployment step prevents storage leaks and reduces confusion on subsequent releases.A practical rollback verification step is to inspect the service’s endpoints and ensure only the expected namespace appears:
kubectl get endpoints myapp -o jsonpath='{.subsets[0].addresses[*].nodeName}'
Output should list nodes from the active half only. If both blue and green addresses appear, the selector change did not propagate fully, and the pipeline should wait before considering the rollback complete.
| Symptom | Likely Cause | Recommended Fix |
|---|---|---|
Green pods Pending 5+ minutes | ImagePullSecret missing or expired | Add --image-pull-secret to the deployment or store credentials in a Secret referenced by imagePullSecrets |
| Service selector change has no effect | Ingress controller does not re-evaluate selectors on the fly | Restart the ingress controller pod or send a SIGHUP if supported; some controllers require a kubectl rollout restart |
| DNS returns old IP after TTL expiry | Downstream resolver ignores TTL or uses cached negative response | Lower TTL on the service’s ClusterIP or use a dedicated DNS provider that respects TTL changes immediately |
| Green deployment consumes more CPU than blue | New image includes profiling or verbose logging disabled in production | Verify DISABLE_PROFILER or equivalent env var; adjust resources.limits in the manifest |
| Rollback takes longer than expected | Blue deployment was scaled to zero or its pods were evicted | Ensure blue deployment’s replicas count is preserved (kubectl scale deployment myapp-blue --replicas=3) before the selector flip |
The pipeline’s strength is its determinism: every action is a declarative manifest change, every failure mode is observable through standard kubectl diagnostics, and every rollback is a single selector reversal. The cost is the maintenance of two full environments and the discipline to keep stateful workloads synchronized. When those conditions are met, blue-green provides a repeatable, low-stress path to production releases with instant revert capability.
This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy