Đang tải...

Ếch Trendy

Cùng chú ếch nhỏ khám phá thế giới Web đầy biến động. Cập nhật trending nhanh như cách ếch đớp mồi! ⌨️🌿


0

DevOps 2026: Docker + GitHub Actions CI/CD Pipeline cho Web Dev từ Zero đến Deploy

Ếch Trendy
Ếch Trendy

3 tháng trước · 8 phút đọc

CI/CD là gì và tại sao bạn cần ngay bây giờ

Mình nhớ hồi mới làm web, deploy app là cả một "nghi lễ": SSH vào server, git pull, npm install, restart service, rồi cầu trời không có lỗi. Mỗi lần mất 20-30 phút. Nếu lỗi thì lại làm từ đầu.

CI/CD (Continuous Integration / Continuous Deployment) giải quyết đúng cái đó. Bạn push code lên GitHub - mọi thứ còn lại tự động: build Docker image, chạy test, deploy lên server. Tổng thời gian? dưới 5 phút.

345527 Từ push code đến production - không cần SSH thủ công

Tại sao 2026 lại quan trọng hơn bao giờ hết? Vì phần lớn công ty tuyển dụng web developer hiện yêu cầu ít nhất hiểu biết cơ bản về CI/CD và containerization. Biết Docker + GitHub Actions không còn là "bonus" - nó là baseline.

Bài này mình sẽ đi từ đầu: giải thích khái niệm, viết Dockerfile, setup GitHub Actions workflow, rồi deploy lên server thật. Không lý thuyết suông.


Docker: đóng gói app một lần, chạy ở đâu cũng được

Docker giải quyết bài toán cổ điển: "code chạy trên máy mình nhưng không chạy trên server". Thay vì cài môi trường trực tiếp lên server, bạn đóng gói toàn bộ app - code, dependencies, runtime - vào một image. Server chỉ cần Docker là chạy được.

Mình sẽ lấy ví dụ với một app Node.js đơn giản:

# Dùng Node 20 alpine cho image nhẹ
FROM node:20-alpine

# Tạo thư mục làm việc trong container
WORKDIR /app

# Copy package files trước để tận dụng Docker layer cache
COPY package*.json ./
RUN npm ci --only=production

# Copy code sau khi đã install dependencies
COPY . .

# Expose port app đang listen
EXPOSE 3000

# Lệnh khởi động app
CMD ["node", "server.js"]

345528 Copy package.json trước giúp cache layer - build nhanh hơn nhiều lần sau

Tại sao copy package*.json trước rồi mới copy code? Docker build theo từng layer. Nếu package.json không đổi, layer npm ci sẽ được cache lại - build lần sau nhanh hơn nhiều. Đây là trick mà nhiều người mới hay bỏ qua.

Một số lưu ý quan trọng:

  • Dùng node:alpine thay vì node:latest - image nhẹ hơn 920-930 MB (Docker Official Node.js Images 2024 Analysis)
  • Dùng npm ci thay vì npm install trong production - đảm bảo version chính xác theo package-lock.json
  • Tạo file .dockerignore để không copy node_modules vào image:
node_modules
.git
.env
*.log

Build và test local trước:

# Build image với tag
docker build -t my-app:latest .

# Chạy container để test
docker run -p 3000:3000 my-app:latest

Nếu bạn chưa quen với Docker và muốn có nền tảng vững hơn, khóa Devops for Engineers cover Docker từ cơ bản đến thực tế - kể cả Linux và networking cần thiết đi kèm.


GitHub Actions: tự động hóa mọi thứ khi push code

GitHub Actions là CI/CD engine được tích hợp thẳng vào GitHub - không cần tool ngoài, không cần server riêng để chạy pipeline. Bạn viết workflow dạng YAML, GitHub tự chạy khi có sự kiện (push, pull request, v.v.).

Cấu trúc một workflow cơ bản:

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
      # Checkout code
      - name: Checkout
        uses: actions/checkout@v4
      
      # Login vào Docker Hub
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}
      
      # Build và push Docker image
      - name: Build and push image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: yourusername/my-app:latest
      
      # SSH vào server và pull image mới
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            docker pull yourusername/my-app:latest
            docker stop my-app || true
            docker rm my-app || true
            docker run -d --name my-app -p 3000:3000 yourusername/my-app:latest

345529 Workflow tự chạy mỗi khi push lên branch main - không cần làm gì thêm

Phần quan trọng nhất là Secrets. Bạn không bao giờ để credential trực tiếp trong file YAML. Vào repo GitHub → Settings → Secrets and variables → Actions, thêm:

  • DOCKER_USERNAME: username Docker Hub
  • DOCKER_TOKEN: access token Docker Hub (không phải password)
  • SERVER_HOST: IP hoặc domain server
  • SERVER_USER: username SSH (thường là ubuntu hoặc root)
  • SERVER_SSH_KEY: private key SSH

Workflow này làm 4 việc theo thứ tự: checkout code → build Docker image → push lên Docker Hub → SSH vào server, pull image mới và restart container. Toàn bộ tự động, bạn chỉ cần git push.


Thêm test vào pipeline: không pass test thì không deploy

Deploy không có test giống như tung code lên production rồi cầu nguyện. Một pipeline tốt phải chặn deploy nếu test fail.

Thêm job test trước job deploy:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Cài dependencies
        run: npm ci
      
      - name: Chạy unit test
        run: npm test
      
      - name: Kiểm tra code style
        run: npm run lint

  build-and-deploy:
    runs-on: ubuntu-latest
    needs: test  # Chỉ chạy khi job test pass
    steps:
      # ... các bước build và deploy như trên

345530 needs: test là chìa khóa - deploy bị block nếu test fail

Keyword needs: test là phần quan trọng nhất. Nó tạo dependency giữa các job: build-and-deploy chỉ chạy khi test pass. Nếu có bất kỳ test nào fail, pipeline dừng lại, bạn nhận thông báo qua email, deploy không xảy ra.

Thực tế mình hay thêm vào:

  • Unit test: npm test hoặc jest
  • Lint: eslint để catch lỗi syntax và code style
  • Type check: tsc --noEmit nếu dùng TypeScript
  • Security audit: npm audit --audit-level=high

Mỗi bước thêm vào mất thêm 1-2 phút run time, nhưng đánh đổi bằng việc không bao giờ push bug lên production. Đáng.


Cấu trúc project hoàn chỉnh và chạy thử end-to-end

Giờ gom lại toàn bộ. Cấu trúc thư mục project với CI/CD setup đầy đủ:

my-app/
├── .github/
│   └── workflows/
│       └── deploy.yml      # GitHub Actions workflow
├── src/
│   ├── server.js
│   └── __tests__/
│       └── server.test.js
├── Dockerfile
├── .dockerignore
├── package.json
└── package-lock.json

Một Dockerfile production-ready hơn với multi-stage build:

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production image - chỉ copy build output
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]

345531 Multi-stage build: stage 1 build, stage 2 chỉ giữ output - image production nhỏ hơn nhiều

Multi-stage build giúp image production không chứa dev dependencies và source code gốc - chỉ có build output. Kết quả image nhỏ hơn và an toàn hơn.

Flow hoàn chỉnh khi bạn làm việc hàng ngày:

  1. Code feature mới ở local
  2. Viết test cho feature đó
  3. git push origin main (hoặc tạo PR)
  4. GitHub Actions tự build Docker image
  5. Chạy test - nếu fail, báo ngay qua email
  6. Nếu pass, push image lên Docker Hub và deploy lên server
  7. Bạn refresh browser và thấy feature mới

Toàn bộ từ bước 3 đến bước 7 mất khoảng 3-7 phút mà bạn không cần làm gì.

Tải về workflow template đầy đủ để dùng ngay: Tải GitHub Actions workflow template


Một số lỗi phổ biến và cách tránh

Mình đã mắc hầu hết những lỗi này. Liệt kê ra để bạn không phải học theo cách đau đớn.

1. Để credential trong code

Tuyệt đối không commit .env file hoặc hardcode password/token vào deploy.yml. Dùng GitHub Secrets cho tất cả credential nhạy cảm. Nhiều người bị leak AWS key vì quên điều này.

2. Không có rollback plan

Deploy xong mới phát hiện lỗi thì sao? Đặt tag version cho Docker image thay vì chỉ dùng latest:

tags: |
  yourusername/my-app:latest
  yourusername/my-app:${{ github.sha }}

Khi cần rollback, chỉ cần docker run yourusername/my-app:[commit-sha-cũ].

3. Container bị restart nhưng data bị mất

Nếu app có data cần persist (uploads, SQLite file, v.v.), phải dùng Docker volume:

docker run -d \
  --name my-app \
  -p 3000:3000 \
  -v /data/uploads:/app/uploads \
  yourusername/my-app:latest

Volume mount giữ data khi container restart hoặc update

4. Không giới hạn resource cho container

Một container có thể ăn hết RAM server nếu có bug. Thêm limits:

docker run -d \
  --name my-app \
  --memory="512m" \
  --cpus="0.5" \
  yourusername/my-app:latest

5. Workflow chạy quá lâu

Cache Docker layers và npm để pipeline nhanh hơn:

- name: Cache Docker layers
  uses: actions/cache@v3
  with:
    path: /tmp/.buildx-cache
    key: ${{ runner.os }}-buildx-${{ github.sha }}

Giờ bạn đã có đủ nền tảng để setup CI/CD pipeline hoàn chỉnh. Bước tiếp:

  1. Tạo repo mới, thêm Dockerfile và .github/workflows/deploy.yml
  2. Setup secrets trong GitHub repo settings
  3. Push code và xem pipeline chạy lần đầu

Gặp lỗi ở bước nào cứ drop comment. Mình sẽ trả lời.

Trang web dùng cookie để hoạt động và đo hiệu quả quảng cáo. Xem chính sách bảo mật