404 Not Found

404 Not Found


nginx

CI/CD — GitHub Actions Automated Delivery Pipeline

CI/CD is like an automated assembly line in an auto factory—parts (code) come in, go through welding (lint), quality inspection (testing), and painting (security scanning), and the finished products (Docker images) that pass inspection are automatically delivered to the dealership (production environment).

1. What You'll Learn


2. Alice's True Story

(1) Pain Point: Manual deployment often leads to errors

After Bob submitted the code, Alice manually fetched the code from the server, ran tests, built the image, and restarted the container. This process took 30 minutes each time, and she often skipped test steps—on one occasion, Alice forgot to run the migrations, and the new endpoint reported a 500 error online. Charlie said he could perform these tasks manually in each of the three environments (dev, staging, and prod), but the risk was too high.

(2) Solution using GitHub Actions

GitHub Actions automatically run the full pipeline—lint → test → build → deploy—with every push or pull request. All steps are defined by code, so nothing can be overlooked, and failures automatically block merges.

(3) Revenue

Deployment time has been reduced from 30 minutes (manual) to 5 minutes (automated), and the issue of missed tests has been completely eliminated (PRs that fail tests cannot be merged), ensuring that deployments across all three environments are completely consistent.


3. GitHub Actions Basics

(1) Core Concepts

100%
flowchart LR
    Trigger[Trigger: push/PR] --> Workflow[Workflow]
    Workflow --> Job1[Job 1: Lint+Test]
    Workflow --> Job2[Job 2: Build]
    Job1 --> Step1[Step: ruff check]
    Job1 --> Step2[Step: pytest]
    Job2 --> Step3[Step: docker build]
    Job2 --> Step4[Step: docker push]
Concept Description Example
Workflow Automated Process Definition .github/workflows/ci.yml
Trigger Trigger Condition push, pull_request
Job Execution unit consisting of a set of steps test, build, deploy
Step Single Operation run: pytest
Action Reusable Actions actions/checkout@v4
Runner Runtime Environment ubuntu-latest

(2) Branch and Environment Mapping

100%
graph TD
    Feature[feature/*] -->|PR| Develop[develop]
    Develop -->|Deploy| DevEnv[Dev Environment]
    Develop -->|PR| Main[main]
    Main -->|Deploy| Staging[Staging Environment]
    Main -->|Tag v*| Prod[Production Environment]
Branch Environment Trigger Deployment Method
feature/* - PR Automated Testing Not Deployed
develop Dev Auto-push Docker Compose
main Staging Auto-push Docker Compose
v* tag Production tag Manual Docker Compose / K8s

4. CI Pipeline

(1) ▶ Example: Complete CI Workflow

YAML
# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install UV
        run: curl -LsSf https://astral.sh/uv/install.sh | sh

      - name: Install dependencies
        run: uv sync --frozen

      - name: Run ruff lint
        run: uv run ruff check app/ tests/

      - name: Run ruff format check
        run: uv run ruff format --check app/ tests/

  test:
    runs-on: ubuntu-latest
    needs: lint
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: pricetracker
          POSTGRES_PASSWORD: test_password
          POSTGRES_DB: pricetracker_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - name: Install UV
        run: curl -LsSf https://astral.sh/uv/install.sh | sh

      - name: Install dependencies
        run: uv sync --frozen

      - name: Run pytest
        env:
          DATABASE_URL: postgresql+asyncpg://pricetracker:test_password@localhost:5432/pricetracker_test
          REDIS_URL: redis://localhost:6379/0
          SECRET_KEY: test-secret-key-for-ci
        run: uv run pytest tests/ -v --cov=app --cov-report=xml

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.xml

  security:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4

      - name: Run safety check
        run: |
          pip install safety
          safety check --json || true

      - name: Run bandit
        run: |
          pip install bandit
          bandit -r app/ -f json || true

  build:
    runs-on: ubuntu-latest
    needs: [test, security]
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: docker/Dockerfile
          push: false
          tags: pricetracker:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Output:

ext CI/CD pipeline loaded Pipeline status: passed Tests: 12 passed, 0 failed


5. CD Pipeline

(1) ▶ Example: Production Deployment Workflow

YAML
# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    tags:
      - 'v*'

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: docker/Dockerfile
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:${{ github.ref_name }}
            ghcr.io/${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /opt/pricetracker
            docker compose pull
            docker compose up -d --remove-orphans
            docker compose exec api alembic upgrade head
            echo "Deployed version ${{ github.ref_name }}"

Output:

TEXT
CONTAINER ID   IMAGE          STATUS         PORTS
abc123         nginx:latest   Up 2 hours     0.0.0.0:80->80/tcp

(2) Complete CI/CD Pipeline Workflow

100%
flowchart LR
    Push[Bob pushes code] --> Lint[Lint: ruff]
    Lint --> Test[Test: pytest]
    Lint --> Sec[Security: safety + bandit]
    Test --> Build[Docker Build]
    Sec --> Build
    Build -->|On tag| Push[Push to GHCR]
    Push --> Deploy[Deploy to Production]
    Deploy --> Health[Health Check]
    Health --> Done[✓ Live]

(2) ▶ Example: Branch Protection and Environmental Protection Rules

YAML
# .github/workflows/branch-protection.yml
name: Branch Protection Check

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Verify PR target is main
        run: |
          if [ "${{ github.base_ref }}" != "main" ]; then
            echo "PR must target main branch"
            exit 1
          fi

      - name: Check environment approval
        if: github.event.pull_request.merged == true
        uses: octokit/request-action@v2
        with:
          route: POST /repos/{owner}/{repo}/deployments
          environment: staging
          required_reviewers: 1

Output:

TEXT
CI/CD pipeline loaded
Pipeline status: passed
Tests: 12 passed, 0 failed

❓ FAQ

Q Is the free quota for GitHub Actions sufficient?
A There are no limits for public repositories. Private repositories get 2,000 free minutes per month. PriceTracker's CI runs for about 5 minutes per run; with 10 runs per day, that's about 1,500 minutes, which is plenty.
Q How should database migrations be handled in CI?
A In CI tests, use Base.metadata.create_all() to create tables directly (without Alembic). Run alembic upgrade head in the CD deployment script.
Q How are secrets securely stored?
A In your GitHub project, go to Settings → Secrets and variables → Actions. SERVER_SSH_KEY, DB_PASSWORD, and others are stored here, and you can reference them in YAML using ${{ secrets.XXX }}.
Q How do I deploy only to the main branch?
A Use on: push: tags: ['v*'] to trigger deployment only when a tag is created. On the development branch, run tests only—do not deploy.
Q How can I take advantage of Docker caching?
A Use cache-from: type=gha to leverage GitHub Actions caching. The initial build takes 5 minutes; subsequent changes only rebuild the modified layers, taking about 1-2 minutes.
Q How do I roll back after a deployment failure?
A docker compose down + docker compose pull <previous-tag> + docker compose up -d. Keep the image tag from the previous version to facilitate rollback.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty ⭐): Create .github/workflows/ci.yml. Set it up so that ruff check and pytest run automatically when you push or create a PR, and verify that the GitHub Actions page shows a green "Passed." Hint: on: push: branches: [main]
  2. Advanced Exercise (Difficulty ⭐⭐): Add PostgreSQL and Redis service containers to the test job, configure environment variables so that pytest can connect to the test database, and add a Docker build step to verify that the image builds successfully. Hint: services: + options: --health-cmd
  3. Challenge (Difficulty: ⭐⭐⭐): Complete CI/CD—The CI pipeline includes linting, testing, security checks, and building; the CD pipeline pushes the image to GHCR when the v* tag is created and deploys it to the server via SSH, using GitHub Secrets to store the keys. Hint: docker/login-action + appleboy/ssh-action + ${{ secrets.XXX }}

---|

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏