CI/CD với GitHub Actions – Tự động test & deploy

 

GitHub Actions cho phép tự động hóa toàn bộ quy trình từ khi push code đến khi deploy lên production. Bài này hướng dẫn xây dựng CI/CD pipeline hoàn chỉnh cho ứng dụng NestJS.

1. GitHub Actions là gì?

  • Workflow: Tập hợp các jobs, được trigger bởi events (push, PR, schedule…)
  • Job: Tập hợp các steps chạy trên cùng một runner
  • Step: Một lệnh shell hoặc một Action có sẵn
  • Runner: Máy ảo chạy job (ubuntu-latest, macos-latest, windows-latest)

2. Pipeline CI — Test & Lint khi push

Tạo file .github/workflows/ci.yml:

name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      # Khởi động MongoDB cho integration test
      mongodb:
        image: mongo:7
        ports:
          - 27017:27017

      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run lint
        run: npm run lint

      - name: Run unit tests
        run: npm run test -- --coverage
        env:
          NODE_ENV: test

      - name: Run e2e tests
        run: npm run test:e2e
        env:
          NODE_ENV: test
          MONGODB_URI: mongodb://localhost:27017/test
          REDIS_HOST: localhost

      - name: Upload coverage report
        uses: codecov/codecov-action@v4
        with:
          token: $

3. Pipeline CD — Deploy lên server

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

on:
  push:
    branches: [main]
  workflow_dispatch:  # Cho phép trigger thủ công

jobs:
  deploy:
    runs-on: ubuntu-latest
    needs: []  # Đặt 'test' ở đây nếu muốn deploy chỉ khi CI pass

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Build
        run: |
          npm ci
          npm run build

      - name: Build Docker image
        run: |
          docker build -t $/my-api:$ .
          docker tag $/my-api:$ \
                     $/my-api:latest

      - name: Login to Container Registry
        uses: docker/login-action@v3
        with:
          registry: $
          username: $
          password: $

      - name: Push Docker image
        run: |
          docker push $/my-api:$
          docker push $/my-api:latest

      - name: Deploy to server via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: $
          username: $
          key: $
          script: |
            docker pull $/my-api:latest
            docker stop my-api || true
            docker rm my-api || true
            docker run -d \
              --name my-api \
              --restart unless-stopped \
              -p 3000:3000 \
              --env-file /home/deploy/.env \
              $/my-api:latest
            docker image prune -f

      - name: Notify Slack on success
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          payload: '{"text":"✅ Deploy thành công: $"}'
        env:
          SLACK_WEBHOOK_URL: $

      - name: Notify Slack on failure
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: '{"text":"❌ Deploy thất bại: $"}'
        env:
          SLACK_WEBHOOK_URL: $

4. Deploy lên AWS ECS

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: $
          aws-secret-access-key: $
          aws-region: ap-southeast-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Push to ECR
        run: |
          IMAGE_URI=$/my-api:$
          docker build -t $IMAGE_URI .
          docker push $IMAGE_URI

      - name: Update ECS Service
        run: |
          aws ecs update-service \
            --cluster my-cluster \
            --service my-api-service \
            --force-new-deployment

5. Cache dependencies để build nhanh hơn

      - name: Cache node_modules
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: $-node-$
          restore-keys: |
            $-node-

      - name: Cache Docker layers
        uses: actions/cache@v4
        with:
          path: /tmp/.buildx-cache
          key: $-buildx-$
          restore-keys: |
            $-buildx-

6. Environment Secrets

Thêm secrets vào GitHub repo: Settings → Secrets and variables → Actions

SERVER_HOST         = 1.2.3.4
SERVER_USER         = ubuntu
SERVER_SSH_KEY      = -----BEGIN RSA PRIVATE KEY-----...
REGISTRY_URL        = ghcr.io/username
REGISTRY_USERNAME   = username
REGISTRY_PASSWORD   = ghp_xxx
SLACK_WEBHOOK       = https://hooks.slack.com/...
AWS_ACCESS_KEY_ID   = AKIA...
AWS_SECRET_ACCESS_KEY = xxx

7. Bảo vệ branch main

Trong Settings → Branches → Branch protection rules:

  • ✅ Require status checks to pass (CI phải xanh)
  • ✅ Require pull request reviews (1-2 reviewer)
  • ✅ Dismiss stale reviews
  • ✅ Require linear history

8. Kết luận

GitHub Actions CI/CD giúp tự động hóa hoàn toàn quy trình phát triển:

  • CI: Tự động lint, test mỗi khi push — phát hiện bug sớm
  • CD: Tự động build Docker image và deploy khi merge vào main
  • Secrets: Bảo mật credential, không hardcode trong code
  • Branch protection: Bắt buộc CI pass trước khi merge

Một pipeline tốt giúp team tự tin deploy nhiều lần mỗi ngày thay vì sợ hãi mỗi lần release.