- Giới thiệu tổng quan
- Yêu cầu chuẩn bị
- Bước 1: Tổ chức dự án Node.js
- Bước 2: Viết Dockerfile
- Bước 3: Cấu hình docker‑compose (tùy chọn)
- Bước 4: Thiết lập GitHub Actions workflow
- Bước 5: Cấu hình máy chủ VPS
- Phần bảo mật và quản lý secret
- Khắc phục các lỗi thường gặp
- Best practices cho CI/CD với Docker
- Kết luận
Giới thiệu tổng quan
Trong môi trường phát triển phần mềm hiện đại, việc tự động hoá quá trình xây dựng, kiểm thử và triển khai (CI/CD) không chỉ giảm thiểu lỗi con người mà còn tăng tốc độ đưa tính năng mới lên môi trường sản xuất. Bài viết này sẽ hướng dẫn chi tiết cách thiết lập một pipeline CI/CD hoàn chỉnh cho một ứng dụng Node.js, sử dụng Docker làm môi trường đóng gói và GitHub Actions làm công cụ tự động hoá, cuối cùng triển khai lên một VPS Linux thông qua SSH.
Yêu cầu chuẩn bị
- Máy tính cá nhân có
gitvàNode.js(phiên bản 18 trở lên) đã cài đặt. - Một tài khoản GitHub và một repository trống để chứa mã nguồn.
- Một VPS Ubuntu 22.04 (hoặc Debian) với quyền
sudovà cài đặtdocker,docker-compose. - Khóa SSH công khai được thêm vào
~/.ssh/authorized_keystrên VPS để GitHub Actions có thể kết nối không cần mật khẩu.
Bước 1: Tổ chức dự án Node.js
Giả sử dự án đã có cấu trúc cơ bản như sau:
my-app/ ├─ src/ │ └─ index.ts ├─ package.json ├─ tsconfig.json └─ .gitignore
Chúng ta sẽ sử dụng TypeScript, vì vậy npm run build sẽ biên dịch mã nguồn sang thư mục dist. Đảm bảo package.json có các script sau:
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node-dev src/index.ts"
}
}Bước 2: Viết Dockerfile
Dockerfile sẽ tạo một image nhẹ dựa trên node:18-alpine, sao chép các file cần thiết, cài đặt phụ thuộc và chỉ định lệnh chạy ứng dụng.
# Dockerfile FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:18-alpine WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/package*.json ./ RUN npm ci --production EXPOSE 3000 CMD ["node", "dist/index.js"]
Ở đây chúng ta dùng multi‑stage build để giảm kích thước image cuối cùng, chỉ chứa các file đã biên dịch và các phụ thuộc production.
Bước 3: Cấu hình docker‑compose (tùy chọn)
Nếu dự án cần một cơ sở dữ liệu (ví dụ PostgreSQL) hoặc các service phụ trợ, chúng ta có thể dùng docker‑compose.yml để quản lý.
version: "3.8"
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
depends_on:
- db
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: secret
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Trong pipeline CI/CD, chúng ta sẽ chỉ build image của service app và đẩy lên Docker registry riêng (hoặc Docker Hub).
Bước 4: Thiết lập GitHub Actions workflow
Tạo file .github/workflows/ci-cd.yml trong repository. Workflow sẽ bao gồm các job:
- Checkout code.
- Setup Node.js và cài đặt phụ thuộc.
- Chạy lint và test (nếu có).
- Build Docker image và đẩy lên Docker Hub.
- SSH vào VPS, pull image mới và khởi động lại container.
Dưới đây là ví dụ chi tiết:
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run lint
run: npm run lint --if-present
- name: Run tests
run: npm test --if-present
- name: Build Docker image
run: |
docker build -t mydockerhubuser/my-app:${{ github.sha }} .
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Push Docker image
run: |
docker push mydockerhubuser/my-app:${{ github.sha }}
- name: Deploy to VPS via SSH
uses: appleboy/[email protected]
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
docker pull mydockerhubuser/my-app:${{ github.sha }}
docker stop my-app || true
docker rm my-app || true
docker run -d --name my-app -p 3000:3000 mydockerhubuser/my-app:${{ github.sha }}
Lưu ý:
- Các biến môi trường nhạy cảm (
DOCKERHUB_USERNAME,DOCKERHUB_TOKEN,VPS_HOST,VPS_USER,VPS_SSH_KEY) phải được lưu trong GitHub Secrets để không lộ ra công khai. - Job
Deploy to VPS via SSHsử dụng actionappleboy/ssh-actionđể thực thi các lệnh trên máy chủ từ xa.
Bước 5: Cấu hình máy chủ VPS
Trên VPS, chúng ta cần cài đặt Docker và Docker Compose (nếu dùng). Dưới đây là các lệnh cài đặt nhanh:
# Cập nhật hệ thống sudo apt-get update && sudo apt-get upgrade -y # Cài Docker sudo apt-get install -y ca-certificates curl gnupg lsb-release sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # Thêm người dùng hiện tại vào nhóm docker để chạy lệnh không sudo sudo usermod -aG docker $USER newgrp docker
Sau khi Docker đã sẵn sàng, chúng ta có thể kiểm tra bằng docker run hello-world. Khi pipeline GitHub Actions thực thi, nó sẽ tự động pull image mới và khởi động container.
Phần bảo mật và quản lý secret
Trong môi trường production, việc bảo mật thông tin nhạy cảm là yếu tố quan trọng. Dưới đây là một số khuyến nghị:
- Sử dụng Docker secrets hoặc environment variables được truyền từ GitHub Actions để cung cấp
DATABASE_URL,JWT_SECRET, v.v. - Không bao giờ commit file
.envchứa secret vào repository. - Giới hạn IP truy cập SSH tới VPS bằng firewall (ufw) hoặc security group.
- Đặt
Dockerfileở chế độUSER node(hoặc một user không root) để giảm rủi ro khi container bị tấn công.
Khắc phục các lỗi thường gặp
1. Lỗi docker: permission denied while trying to connect to the Docker daemon socket
Nguyên nhân thường là action SSH chạy dưới user không thuộc nhóm docker. Giải pháp: thêm user vào nhóm docker và khởi động lại SSH session, hoặc trong script sử dụng sudo docker ….
2. Lỗi manifest unknown: manifest unknown khi push image
Điều này xảy ra khi tag image không khớp với tên repository trên Docker Hub. Kiểm tra lại biến DOCKERHUB_USERNAME và tên repository.
3. Container không khởi động do cổng đã bị chiếm dụng
Sử dụng docker ps -a để kiểm tra container cũ, dừng và xóa chúng trước khi chạy container mới, hoặc thay đổi cổng mapping.
Best practices cho CI/CD với Docker
- Luôn dùng multi‑stage build để giảm kích thước image.
- Tag image bằng
git SHAvàlatestđể dễ rollback. - Sử dụng healthcheck trong Dockerfile để GitHub Actions có thể xác định container đã sẵn sàng chưa.
- Giữ workflow ngắn gọn, tách riêng job
testvàdeployđể tránh việc triển khai khi test thất bại. - Thiết lập rollback strategy bằng cách giữ lại image cũ và dùng
docker tagđể quay lại phiên bản ổn định.
Kết luận
Việc thiết lập một pipeline CI/CD tự động cho ứng dụng Node.js trên VPS không chỉ giúp giảm thiểu lỗi triển khai mà còn tạo ra quy trình phát triển nhất quán, nhanh chóng và an toàn. Khi đã nắm vững các bước từ viết Dockerfile, cấu hình GitHub Actions, đến bảo mật SSH và quản lý secret, bạn sẽ có thể mở rộng quy trình này cho các dự án phức tạp hơn, tích hợp database, cache, hoặc micro‑service.
Để nâng cao kiến thức và thực hành chi tiết hơn, Tham khảo khóa học "Deploy Web - Triển khai website lên môi trường Internet" tại đây.






