惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
Recorded Future
Recorded Future
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The GitHub Blog
The GitHub Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google DeepMind News
Google DeepMind News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Microsoft Azure Blog
Microsoft Azure Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
M
MIT News - Artificial intelligence
云风的 BLOG
云风的 BLOG
Y
Y Combinator Blog
N
News | PayPal Newsroom
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Help Net Security
Help Net Security
博客园 - Franky
SecWiki News
SecWiki News
Recent Announcements
Recent Announcements
T
Troy Hunt's Blog
The Register - Security
The Register - Security
The Last Watchdog
The Last Watchdog
Webroot Blog
Webroot Blog
S
Security Affairs
博客园 - 司徒正美
S
Schneier on Security
I
InfoQ
博客园_首页
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Threat Research - Cisco Blogs
Forbes - Security
Forbes - Security
腾讯CDC
N
Netflix TechBlog - Medium
N
News and Events Feed by Topic
Cloudbric
Cloudbric
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
A
About on SuperTechFans
Engineering at Meta
Engineering at Meta
Recent Commits to openclaw:main
Recent Commits to openclaw:main
B
Blog
V
Vulnerabilities – Threatpost
C
Check Point Blog
Google DeepMind News
Google DeepMind News
Google Online Security Blog
Google Online Security Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
C
Cisco Blogs
Schneier on Security
Schneier on Security
O
OpenAI News
K
Kaspersky official blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Your Deployments Are Causing Downtime. Mine Do Not. Here Is Why
Oluwagbade Odimayo · 2026-05-31 · via DEV Community

Every engineer has a war story about a deployment gone wrong.

Mine came before I built this project. A Friday afternoon release, a ten-minute window where the app was completely unreachable, and an inbox full of user complaints by the time it recovered. The fix was already deployed. The damage was already done.

I built this project to make sure that never happens again.

What you are about to read is a complete, honest walkthrough of how I built a blue-green deployment pipeline on AWS EKS from scratch, on Ubuntu, with nothing but a terminal and determination. I will show you the actual commands, the real challenges I hit, and the exact solutions that fixed them. By the end you will have a working mental model and a repository you can fork today.

The pipeline averages 29 seconds end to end. The traffic switch itself takes under one second. Rollback takes under five seconds. And I have the screenshots to prove every single claim.


What Blue-Green Deployment Actually Means

Before we touch a single terminal command, let us get the concept locked in because it is simpler than most articles make it sound.

You run two identical production environments at all times. One is called blue. One is called green. At any given moment, one is live (serving all real user traffic) and the other is idle (running, healthy, but not serving anyone).

When you want to release a new version:

  1. Deploy the new version to the idle environment
  2. Verify it is healthy
  3. Flip a switch that moves all traffic from live to idle

That switch is instantaneous. Users experience nothing. If the new version has a critical bug, you flip the switch back. Rollback is not a redeploy. It is the same switch command in reverse.

On Kubernetes, that switch is a single field change in a Service manifest. That is genuinely all it is.

kubectl patch service bluegreen-service \
  -p '{"spec":{"selector":{"app":"bluegreen-app","version":"green"}}}'

One command. One second. Zero downtime.


The Stack

Before we dive into the build, here is what the complete system uses:

Layer Technology
Application Node.js 20 + Express
Container Docker (multi-stage build)
Orchestration Kubernetes on AWS EKS
Registry Amazon ECR
Ingress NGINX Ingress Controller
CI/CD GitHub Actions
Cloud Amazon Web Services

The application itself is intentionally minimal. A Node.js Express server with two endpoints: /health and /. The health endpoint is what makes everything else possible. The main page displays a giant color and version number so the traffic switch is visually obvious in a browser.


The Architecture

Here is how every component connects:

Developer
    |
    | git push to main
    v
GitHub Actions (29 seconds average)
    |
    +-- Configure AWS credentials
    +-- Log in to Amazon ECR
    +-- Connect kubectl to EKS
    +-- Detect idle environment
    +-- Build image and push to ECR
    +-- Deploy to idle environment
    +-- Health check idle pods
    +-- Switch traffic (the selector patch)
    |
    v
Amazon EKS Cluster
    |
    +-- NGINX Ingress (public AWS ELB URL)
            |
            v
    Kubernetes Service (selector: version=blue OR version=green)
            |
            +-- Blue Deployment  (2 pods, v1.0.0)
            +-- Green Deployment (2 pods, v2.0.0)

The public URL never changes. The NGINX Ingress forwards to the Service. The Service routes to whichever environment the selector points at. Everything else is just automation around that one field.


The Application

const express = require("express");
const app = express();

const PORT        = process.env.PORT        || 3000;
const APP_VERSION = process.env.APP_VERSION || "v1.0.0";
const APP_COLOR   = (process.env.APP_COLOR  || "blue").toLowerCase();
const COLOR_HEX   = APP_COLOR === "green" ? "#2C9E5E" : "#1C6EA4";

app.get("/health", (req, res) => {
  res.status(200).json({
    status:  "healthy",
    color:   APP_COLOR,
    version: APP_VERSION,
  });
});

app.get("/", (req, res) => {
  // Returns full-page HTML showing which environment served this request
  // The giant color and version number make the switch visually undeniable
});

app.listen(PORT, () =>
  console.log(`color=${APP_COLOR} version=${APP_VERSION} port=${PORT}`)
);

APP_COLOR and APP_VERSION are injected by Kubernetes from the Deployment manifest. When blue is live, every response carries "color":"blue". The moment traffic switches, responses immediately return "color":"green". This is what you watch during the live demo.

Why the Health Endpoint Matters So Much

Kubernetes readiness probes call /health on a schedule. A pod is only added to the Service routing pool after its readiness probe passes. This means Kubernetes will never route traffic to a pod that has not already proven it can respond correctly.

But that is not enough on its own. The CI/CD pipeline also calls /health manually from inside an idle pod before touching the Service selector. Two independent verification layers before any user traffic moves.


The Dockerfile

# Stage 1: install dependencies only
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev

# Stage 2: runtime image
FROM node:20-alpine AS runtime
WORKDIR /app

# Non-root user for security
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN chown -R appuser:appgroup /app
USER appuser

EXPOSE 3000
ENV PORT=3000 APP_COLOR=blue APP_VERSION=v1.0.0 NODE_ENV=production

CMD ["node", "server.js"]

Three decisions worth explaining.

First, the multi-stage build. The final image contains only what is needed to run the app. Build tools, npm cache, and intermediate artifacts from the deps stage are discarded. Smaller image, faster pulls, smaller attack surface.

Second, the non-root user. Running containers as root is a well-documented security risk. Creating a dedicated user takes four lines and meaningfully limits blast radius if the application is ever compromised.

Third, no HEALTHCHECK instruction. We rely on Kubernetes probes instead because they integrate directly with the pod lifecycle and the rollout gating logic.


The Kubernetes Manifests

Four files. Each has a specific, non-overlapping responsibility.

deployment-blue.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: bluegreen-blue
  labels:
    app: bluegreen-app
    version: blue
spec:
  replicas: 2
  selector:
    matchLabels:
      app: bluegreen-app
      version: blue
  template:
    metadata:
      labels:
        app: bluegreen-app
        version: blue
    spec:
      containers:
        - name: bluegreen-app
          image: 677276115158.dkr.ecr.us-east-1.amazonaws.com/bluegreen-app:blue
          env:
            - name: APP_COLOR
              value: "blue"
            - name: APP_VERSION
              value: "v1.0.0"
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "250m"
              memory: "256Mi"
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 15

The version: blue label is everything. The Service selector matches on it. Resource limits are non-negotiable in production. Without them a misbehaving pod can consume all node resources and starve other workloads.

The green Deployment is identical except for version: green, APP_COLOR=green, and APP_VERSION=v2.0.0.

service.yaml (The Traffic Switch)

apiVersion: v1
kind: Service
metadata:
  name: bluegreen-service
spec:
  type: ClusterIP
  selector:
    app: bluegreen-app
    version: blue   # THE SWITCH. Change to "green" to move all traffic.
  ports:
    - port: 80
      targetPort: 3000

This is the entire mechanism. Change version: blue to version: green and every new connection goes to green. In-flight connections on blue drain naturally. The pipeline does this with:

kubectl patch service bluegreen-service \
  -p '{"spec":{"selector":{"app":"bluegreen-app","version":"green"}}}'


The CI/CD Pipeline

This is where everything comes together. The full deploy.yml workflow runs on every push to main.

The Detect Idle Step (The Heart of the Pipeline)

LIVE=$(kubectl get service bluegreen-service \
  -o jsonpath='{.spec.selector.version}' 2>/dev/null || echo "blue")

if [ "$LIVE" = "blue" ]; then IDLE="green"; else IDLE="blue"; fi

echo "live=$LIVE" >> $GITHUB_OUTPUT
echo "idle=$IDLE" >> $GITHUB_OUTPUT

Rather than hard-coding which environment to deploy to, the pipeline reads the current cluster state and computes the answer. Run it a hundred times and it always deploys to the correct idle environment. The || echo "blue" fallback handles first-time runs where the Service does not exist yet.

The Health Check Gate

POD=$(kubectl get pods -l version=${IDLE} \
  -o jsonpath='{.items[0].metadata.name}')

kubectl exec "$POD" -- wget -qO- http://localhost:3000/health | grep '"status":"healthy"'

Running the health check with kubectl exec means the request goes directly to the pod's localhost, bypassing the Service and Ingress entirely. If grep does not find "status":"healthy", it exits non-zero, the pipeline step fails, and the traffic switch step never runs. Users see nothing.

The Traffic Switch

kubectl patch service bluegreen-service \
  -p "{\"spec\":{\"selector\":{\"app\":\"bluegreen-app\",\"version\":\"${IDLE}\"}}}"

echo "Traffic is now live on: ${IDLE}"

That single patch command is the entire release. Everything before it was verification. Everything after it is cleanup.


The Challenges I Actually Hit (And How I Fixed Them)

Challenge 1: AWS gives you a hostname, not an IP address

Every tutorial I found used jsonpath='{.status.loadBalancer.ingress[0].ip}' to get the Ingress address. On AWS, that returns empty. AWS ELB gives you a hostname like:

ae91af9007367451cbf51b4cfb8fb340-1660787795.us-east-1.elb.amazonaws.com

I spent time staring at curl: (6) Could not resolve host: health before realising the INGRESS_IP variable was empty and curl was trying to resolve health as a hostname.

Fix: Use .hostname instead of .ip:

export INGRESS_HOST=$(kubectl get ingress bluegreen-ingress \
  -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')

curl http://$INGRESS_HOST/health

Store it as INGRESS_HOST not INGRESS_IP so the name never misleads you.


Challenge 2: Pods stuck in ImagePullBackOff

After deploying the manifests, all four pods hit ImagePullBackOff. The cluster could not pull the images from ECR because the node IAM role had no permission to access the private registry.

This is the step most tutorials either skip or bury in a footnote.

Fix: Find the node instance role and attach the ECR read policy:

ROLE_NAME=$(aws iam list-roles \
  --query "Roles[?contains(RoleName,'NodeInstanceRole')].RoleName" \
  --output text)

aws iam attach-role-policy \
  --role-name  "$ROLE_NAME" \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly

No credentials hard-coded anywhere. The node role handles authentication transparently.


Challenge 3: The workflow files were never actually in the repo

I pushed the project to GitHub, went to the Actions tab, and saw the "Get started with GitHub Actions" page. The workflow files existed on my machine but the .github/workflows/ folder was empty because I had created the files in a downloaded ZIP earlier and never actually written them on the Ubuntu machine.

Fix: Create the files directly on Ubuntu using heredocs and explicitly add the hidden folder:

cat > .github/workflows/deploy.yml << 'EOF'
# ... workflow content
EOF

git add .github/
git commit -m "Add GitHub Actions CI/CD workflows"
git push

Always verify with ls -la .github/workflows/ before pushing. Hidden folders are easy to miss.


Challenge 4: The curl loop did not capture the switch moment

During the first demo run, I started the curl loop after the switch had already happened. Terminal 1 only showed green responses. No evidence of the transition.

Fix: Start the monitoring loop first, then trigger the switch. The loop must already be running before the patch command fires. With sleep 1 between requests, the transition window is narrow. Setting sleep to 0.5 or removing it entirely makes the switch moment easier to capture.


Proving Zero Downtime Live

This is the most important part of the project and the most satisfying moment.

Open two terminals.

Terminal 1:

while true; do
  curl -s http://$INGRESS_HOST/health
  echo
  sleep 1
done

Terminal 2:

kubectl patch service bluegreen-service \
  -p '{"spec":{"selector":{"app":"bluegreen-app","version":"green"}}}'

Watch Terminal 1. You will see:

{"status":"healthy","color":"blue","version":"v1.0.0"}
{"status":"healthy","color":"blue","version":"v1.0.0"}
{"status":"healthy","color":"green","version":"v2.0.0"}
{"status":"healthy","color":"green","version":"v2.0.0"}

The color changes. The status never wavers. Not a single failed request. That line where blue becomes green is your zero-downtime proof.

Open the same URL in a browser before and after. Full blue page becomes full green page. Same URL. Same cluster. Different version. Zero downtime.


The GitHub Actions Pipeline in Action

After pushing the workflow files, every commit to main triggers the full pipeline automatically. Here is what a successful run looks like:

Set up job              1s
Checkout                1s
Configure AWS credentials   0s
Log in to Amazon ECR    1s
Connect kubectl to EKS  4s
Detect idle environment 3s
Build and push image to ECR  10s
Deploy to idle environment   3s
Health check idle pods  2s
Switch traffic          1s
Complete job            0s

Total: 29 seconds

Every step green. Every step automated. No manual intervention. No SSH into servers. No hand-run scripts.

The rollback workflow is equally clean. Go to Actions, select Rollback, choose the target environment, click Run workflow. Traffic switches back in under 5 seconds.


Key Takeaways

The mechanism is simpler than you think. The entire blue-green strategy is one label selector field in a Kubernetes Service. Everything else is automation around that field.

Dynamic detection beats hard-coding. Reading the live cluster state to find the idle environment makes the pipeline self-correcting and safe to run repeatedly without manual tracking of which environment is currently live.

Two verification layers beat one. Kubernetes readiness probes are necessary but not sufficient. An explicit health check from inside the idle pod, before the selector patch, adds a second independent gate that catches edge cases probes miss.

AWS is not Azure. If you have done this on Azure before, expect a few AWS-specific surprises: hostnames instead of IPs for load balancers, IAM role policies instead of registry attachment flags, and eksctl instead of az aks create. The concepts are identical. The commands differ.

Screenshots are not optional. If you are building this for a portfolio or a capstone, capture every stage while the cluster is live. Once you tear it down, the URL is gone and the proof is gone with it.


The Repository

The full source code is at:

github.com/gbadedata/zero-downtime-bluegreen-eks

It includes all Kubernetes manifests, the complete GitHub Actions workflows, the Dockerfile, the application code, and ten screenshots from the live deployment. Fork it, adapt it, and build on it.


What I Would Add Next

Prometheus and Grafana for real-time metrics during the switch. Right now the only signal is the curl loop. A proper monitoring stack would give error rate, latency, and request volume dashboards that make the switch moment quantifiable.

Canary releases as a step between all-or-nothing blue-green and fully automated rollout. Send 5% of traffic to green, monitor for 10 minutes, then ramp to 100%. Achievable with NGINX Ingress weight annotations.

Terraform for infrastructure so the EKS cluster, ECR repository, IAM roles, and VPC are all version-controlled and reproducible with a single terraform apply.

Automated rollback triggered by a post-switch error rate spike. If the error rate on the new environment exceeds a threshold within two minutes of the switch, automatically patch the selector back. No human required.