CI/CD Pipeline: The 'Factory Assembly Line' Mental Model
Why does 'works on my machine' still happen with Docker? A mastery guide to CI/CD pipelines, GitHub Actions, and zero-downtime Blue/Green deployments.
Every team starts with the same deployment process:
- Write code on your laptop.
- Zip it up.
- SSH into the server.
- Cross your fingers and pray.
- Something breaks in production at 2 AM.
CI/CD exists to make Step 3–5 automatic, repeatable, and less terrifying.
This is the Mastery Guide to CI/CD. We’ll use the “Factory Assembly Line” model to understand pipelines, GitHub Actions, and the art of zero-downtime deployments.
Part 1: Foundations (The Mental Model)
The Old Way: Artisan Crafting
In a blacksmith shop, one guy does everything: heats the metal, shapes it, cools it, inspects it, packages it. If he’s sick, nothing ships. If he makes a mistake, it goes to the customer.
This is manual deployment.
The New Way: The Factory Assembly Line
A modern factory has stations: Stamping → Welding → Painting → Inspection → Packaging. Each station checks the output of the previous one. If Inspection fails, nothing reaches Packaging.
This is CI/CD:
Code Commit
│
▼
[Station 1: CI — Continuous Integration]
├── Run Tests (Did we break anything?)
├── Lint Code (Is the code clean?)
└── Build Docker Image (Does it even compile?)
│
▼
[Station 2: CD — Continuous Delivery]
├── Push Image to Registry
└── Deploy to Staging Environment
│
▼
[Station 3: CD — Continuous Deployment] ← (Optional: fully automated)
└── Deploy to Production
Part 2: The Investigation (GitHub Actions Anatomy)
GitHub Actions is the most popular CI/CD tool for developers. A “workflow” is just a YAML file in .github/workflows/.
Reading a Pipeline
# .github/workflows/deploy.yml
name: "Build and Deploy"
# TRIGGER: When does this run?
on:
push:
branches: [main] # Only on pushes to main
jobs:
# JOB 1: The CI (Test & Build)
build:
runs-on: ubuntu-latest # What machine to run on (GitHub provides this)
steps:
- name: "Checkout Code"
uses: actions/checkout@v4 # Built-in Action: Pull the code
- name: "Run Tests"
run: |
pip install -r requirements.txt
pytest tests/ --tb=short
- name: "Build Docker Image"
run: docker build -t myapp:${{ github.sha }} .
- name: "Push to Registry"
run: |
docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
docker push ghcr.io/myrepo/myapp:${{ github.sha }}
# JOB 2: The CD (Deploy) — Only runs if build succeeds
deploy:
needs: build # ← This job WAITS for build to pass
runs-on: ubuntu-latest
steps:
- name: "Deploy to Server"
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: deploy
key: ${{ secrets.SSH_KEY }}
script: |
docker pull ghcr.io/myrepo/myapp:${{ github.sha }}
docker stop myapp || true
docker run -d --name myapp -p 8000:8000 ghcr.io/myrepo/myapp:${{ github.sha }}
Key Concepts to Spot:
on:— The trigger. (push,pull_request,schedule).jobs:— Independent units of work that CAN run in parallel.needs:— Creates a dependency (Job B waits for Job A).secrets:— Secrets stored in GitHub settings. Never hardcode passwords in YAML!
Part 3: The Diagnosis (Common Failures)
The “Works in CI, Fails in Production” Mystery
| Problem | Cause | Fix |
|---|---|---|
| Different dependencies | CI installs fresh. Production has leftovers. | Docker everything. Immutable artifacts. |
| Different environment variables | CI has DEBUG=True. Prod doesn’t. | Use env: in GitHub Actions and match production secrets. |
| Database migrations forgot | Code deployed, migration not run. | Add a migration step BEFORE the deploy step. |
| Tests pass but app crashes | Tests don’t cover the startup code. | Add a smoke test: curl http://localhost:8000/health after deploy. |
Part 4: The Resolution (Deployment Strategies)
Not all deployments are equal. The strategy determines the risk.
Strategy 1: Rolling Deployment (Kubernetes Default)
Replace old pods one by one. Minimal risk. If one fails, stop.
Strategy 2: Blue/Green Deployment (Zero Downtime)
Run TWO identical production environments. “Blue” is live. “Green” is the new version.
Load Balancer
│
├──► Blue (v1) ← LIVE (100% traffic)
│
└──► Green (v2) ← Staging (0% traffic)
After deploy:
│
├──► Blue (v1) ← Idle (0% traffic)
│
└──► Green (v2) ← LIVE (100% traffic)
- Benefit: Instant rollback. If Green fails, flip the switch back to Blue.
- Cost: Requires double the infrastructure.
Strategy 3: Canary Deployment (Progressive)
Slowly roll out to a percentage of users. If no errors, increase to 100%.
Load Balancer
│
├──► v1 (90% traffic)
│
└──► v2 (10% traffic "Canary") ← Test on real users
Final Mental Model
CI (Continuous Integration) -> The Quality Inspection. "Does this code compile and pass tests?"
CD (Continuous Delivery) -> The Packaging. "Is this artifact ready to be deployed?"
CD (Continuous Deployment) -> The Shipping. "Deploy automatically to production."
Blue/Green -> Two factories. Instant switch.
Canary -> Test on 10% of real customers first.
Rolling -> Replace workers one by one.
Rules for Good CI/CD:
- Fast feedback: The pipeline should fail in under 5 minutes if there’s a problem.
- Everything is code: The pipeline config is in Git. No clicking in Jenkins UI.
- Immutable artifacts: Build once, deploy the same image everywhere. Never
git pullon a production server.
Related posts
-
Docker vs. Kubernetes: The 'Hotel Manager' Mental Model
Why do I need K8s if I have Docker? A mastery guide to understanding Containers (Robots) vs Orchestration (The Manager).
-
Observability: Logs vs. Metrics vs. Tracing — The 'Doctor's Kit' Mental Model
Your app is slow. Is it the DB? The queue? The network? A mastery guide to the three pillars of observability and how they work together.
-
Yellorn: The Browser Tab I Wanted for Dirty Data and Webhook Debugging
A human tour of Yellorn: repair broken JSON, XML, YAML, and CSV, publish mock webhook APIs, send HTTP requests, and keep integration debugging in one focused browser workspace.
-
Production Google OAuth In One Prompt: I Let Cursor Drive Chrome DevTools MCP
I asked an AI agent to wire production-grade Google OAuth into yellorn.com. One prompt later, it had clicked through Google Cloud Console, configured the consent screen, registered the OAuth client, stored the secrets in Cloudflare, and shipped to prod. I just sipped coffee.