CI/CD pipeline tốt là khoản đầu tư có ROI cao nhất cho team backend. Mỗi PR được test tự động, deploy 1-click, rollback dễ. Bài này đi qua workflow GitHub Actions production-ready cho Node.js, từ lint đến deploy K8s.
CI vs CD
- CI (Continuous Integration): mỗi commit tự động lint + test + build. Catch bug sớm.
- CD (Continuous Delivery/Deployment): build artifact tự deploy đến staging (delivery) hoặc production (deployment).
CI workflow đầy đủ
# .github/workflows/ci.yml
name: CI
on:
pull_request:
push: { branches: [main] }
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
lint-test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [22, 24]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: 'npm' # auto-cache theo package-lock.json hash
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test -- --coverage
- uses: codecov/codecov-action@v4
if: matrix.node == '22'
with: { files: ./coverage/coverage-final.json }
e2e:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env: { POSTGRES_PASSWORD: test }
ports: ['5432:5432']
options: --health-cmd pg_isready --health-interval 5s
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: 'npm' }
- run: npm ci
- run: npm run db:migrate
env: { DATABASE_URL: postgres://postgres:test@localhost/postgres }
- run: npm run test:e2e
env: { DATABASE_URL: postgres://postgres:test@localhost/postgres }
concurrency cancel CI run cũ khi push commit mới — tiết kiệm minute.
Build Docker image với cache
build-image:
needs: [lint-test, e2e]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=sha,prefix=
type=raw,value=latest,enable={{is_default_branch}}
- uses: docker/build-push-action@v5
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
BuildKit + GitHub Actions cache giảm rebuild từ 5 phút còn 30 giây cho thay đổi nhỏ.
CD đến staging (auto)
deploy-staging:
needs: build-image
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.alodev.vn
permissions:
id-token: write # OIDC
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE }}
aws-region: ap-southeast-1
- run: |
aws eks update-kubeconfig --name alodev-staging
kubectl set image deployment/alodev-api app=ghcr.io/${{ github.repository }}:${{ github.sha }}
kubectl rollout status deployment/alodev-api --timeout=2m
OIDC: aws-actions/configure-aws-credentials mint token short-lived. Không có AWS_ACCESS_KEY trong secret.
CD production (require approval)
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://alodev.vn
# Environment 'production' đã setup require_review trong UI:
# Settings → Environments → production → Required reviewers
steps:
- uses: aws-actions/configure-aws-credentials@v4
with: { role-to-assume: ${{ vars.AWS_PROD_DEPLOY_ROLE }} }
- name: Database migration
run: ./scripts/migrate-prod.sh
- name: Deploy
run: |
kubectl set image deployment/alodev-api app=ghcr.io/${{ github.repository }}:${{ github.sha }} -n production
kubectl rollout status deployment/alodev-api -n production
- name: Smoke test
run: curl -f https://alodev.vn/healthz
Reviewer click "Approve" trong PR run page mới deploy. Có thể setup branch protection: chỉ main mới deploy được prod.
Reusable workflow
Logic deploy giống nhau giữa staging/prod → tách reusable:
# .github/workflows/deploy.yml
on:
workflow_call:
inputs:
environment: { required: true, type: string }
cluster: { required: true, type: string }
secrets:
AWS_ROLE: { required: true }
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
# ... shared deploy logic
# Caller workflow:
deploy-staging:
uses: ./.github/workflows/deploy.yml
with:
environment: staging
cluster: alodev-staging
secrets:
AWS_ROLE: ${{ secrets.STAGING_AWS_ROLE }}
Best practices
- ✅ Fail fast: lint + typecheck trước test (phát hiện bug 5s vs 5 phút)
- ✅ Cache deps theo lockfile, Docker build theo gha cache
- ✅ Concurrency cancel in-progress khi push commit mới
- ✅ OIDC thay static secret cho cloud auth
- ✅ Environment + required review cho production
- ✅ Smoke test sau deploy — fail thì rollback tự động
- ✅ Notification Slack khi deploy success/fail
Kết luận
CI/CD pipeline tốt là tự động hoá tin cậy: PR review xong, click merge, 5 phút sau code đã chạy production. Bắt đầu đơn giản với CI lint + test, thêm CD khi confident. GitHub Actions là lựa chọn default cho repo trên GitHub. Tham khảo Database migration safe để deploy schema change không downtime, và Blue-green deployment cho zero-risk rollback.