---
title: "CI/CD Pipeline: The 'Factory Assembly Line' Mental Model"
description: "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."
pubDate: "2023-05-25"
lang: "en"
slug: "cicd-pipeline-github-actions"
url: "https://hoangyell.com/cicd-pipeline-github-actions/"
author: "HoangYell"
translation_vi: "https://hoangyell.com/vi/cicd-pipeline-github-actions/"
categories: ["Developer Tools"]
tags: ["devops", "ci-cd", "github-actions", "docker"]
heroImage: "https://raw.githubusercontent.com/HoangGeek/store/main/webp/photo-1621839673705-6617adf9e890.webp"
---

Every team starts with the same deployment process:

1. Write code on your laptop.
2. Zip it up.
3. SSH into the server.
4. Cross your fingers and pray.
5. 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:

```markdy
scene type=flowchart width=1200 height=750
theme: light=doodle dark=nebula
layout TB

repo GitCommit "Git Commit (main)"
service LintTest "Lint & Unit Tests"
service DockerBuild "Docker Build & OCI Push"
service SecurityScan "SAST & Vulnerability Gate" focal=true
service DeployStaging "Staging Smoke Test"
service DeployProd "Production Canary Release"

beat pipeline_flow "Automated Factory Stages" {
  show $nodes stagger=50ms
  GitCommit -> LintTest "trigger webhook"
  LintTest -> DockerBuild "tests pass"
  DockerBuild -> SecurityScan "artifact ready"
  SecurityScan -> DeployStaging "no CVEs detected"
  DeployStaging -> DeployProd "canary gate approved"
  glow SecurityScan "Zero-trust vulnerability inspection"
}

player:
    playback:
      loop true
    controls:
      speed true
      speeds "0.25"
      fullscreen true
      gif true
      theme true
    interaction:
      zoom true
      pan true
      click_to_play true
      double_click_to_reset true
      keyboard false
    chrome:
      badge true
      progress none
```

***

## 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

```yaml
# .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**:
1.  **Fast feedback**: The pipeline should fail in under 5 minutes if there's a problem.
2.  **Everything is code**: The pipeline config is in Git. No clicking in Jenkins UI.
3.  **Immutable artifacts**: Build once, deploy the same image everywhere. Never `git pull` on a production server.
