Giới thiệu
Trong môi trường phát triển phần mềm hiện đại, việc tự động hoá quy trình xây dựng, kiểm thử và triển khai (CI/CD) đã trở thành tiêu chuẩn bắt buộc để giảm thiểu rủi ro, tăng tốc độ đưa sản phẩm ra thị trường và duy trì chất lượng mã nguồn. Bài viết này sẽ tập trung vào cách thiết lập một pipeline CI/CD hoàn chỉnh cho Strapi CMS – một Headless CMS mã nguồn mở – sử dụng Docker làm môi trường container hoá và GitHub Actions làm công cụ tự động hoá. Nội dung sẽ đi sâu vào từng bước, cung cấp các ví dụ thực tế, so sánh ưu nhược điểm và đưa ra những best practices dành cho các team phát triển backend Node.js.
Tổng quan về CI/CD và Strapi
CI/CD là gì?
CI (Continuous Integration) là quá trình tích hợp mã nguồn mới vào nhánh chính một cách thường xuyên, kèm theo việc chạy các kiểm thử tự động để phát hiện lỗi sớm. CD (Continuous Delivery/Deployment) mở rộng CI bằng cách tự động hoá việc đóng gói và triển khai ứng dụng lên môi trường staging hoặc production. Khi CI/CD được thiết lập đúng, mỗi commit sẽ trải qua một chuỗi các bước: lint, test, build, và deploy, giúp giảm thiểu thời gian phản hồi và tăng độ tin cậy.
Strapi trong kiến trúc Headless CMS
Strapi là một framework Node.js cho phép tạo nhanh các API REST hoặc GraphQL, đồng thời cung cấp giao diện quản trị nội dung. Vì Strapi dựa trên Node.js và Express, việc container hoá bằng Docker và chạy trong môi trường CI/CD là hoàn toàn khả thi. Ngoài ra, Strapi hỗ trợ plugin, middleware và các hook tùy chỉnh, cho phép mở rộng chức năng mà không làm phức tạp quá trình build.
Chuẩn bị môi trường phát triển
Cài đặt Docker và Docker Compose
Trước khi xây dựng pipeline, cần đảm bảo máy phát triển và máy CI có cài Docker (phiên bản >= 20.10) và Docker Compose (phiên bản >= 2.0). Docker cung cấp khả năng cô lập môi trường, giúp tránh các xung đột phụ thuộc giữa các dự án.
docker --version docker-compose --version
Tạo dự án Strapi
Giả sử bạn chưa có dự án Strapi, có thể khởi tạo nhanh bằng lệnh sau:
npx create-strapi-app my-cms \ --quickstart \ --no-run
Sau khi dự án được tạo, cấu trúc thư mục sẽ bao gồm src, config, node_modules và file package.json. Để chuẩn bị cho Docker, chúng ta sẽ tạo một Dockerfile và một file docker-compose.yml cho môi trường development.
Dockerfile cho Strapi
Dockerfile sẽ dựa trên image node:18-alpine, cài đặt các phụ thuộc, sao chép mã nguồn và chạy lệnh npm run develop khi khởi động container.
FROM node:18-alpine # Thiết lập thư mục làm việc WORKDIR /app # Sao chép package.json và lock file để tận dụng layer cache COPY package*.json ./ # Cài đặt phụ thuộc RUN npm ci --production # Sao chép toàn bộ mã nguồn COPY . . # Thiết lập biến môi trường ENV NODE_ENV=production # Expose cổng mặc định của Strapi EXPOSE 1337 # Lệnh khởi động CMD ["npm", "run", "start"]
docker-compose.yml cho môi trường development
File này sẽ định nghĩa hai service: strapi và postgres (hoặc sqlite nếu muốn đơn giản). Đối với môi trường production, chúng ta sẽ tách riêng file compose.
version: "3.8"
services:
strapi:
build: .
ports:
- "1337:1337"
environment:
- DATABASE_CLIENT=postgres
- DATABASE_HOST=postgres
- DATABASE_PORT=5432
- DATABASE_NAME=strapi
- DATABASE_USERNAME=strapi
- DATABASE_PASSWORD=strapi
depends_on:
- postgres
volumes:
- .:/app
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_DB=strapi
- POSTGRES_USER=strapi
- POSTGRES_PASSWORD=strapi
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:Sau khi cấu hình xong, chạy docker-compose up -d để khởi động môi trường local. Việc này sẽ giúp chúng ta có một môi trường giống hệt như CI, giảm thiểu “works on my machine”.
Xây dựng pipeline GitHub Actions
Cấu trúc workflow
Trong GitHub, workflow được định nghĩa bằng file YAML nằm trong thư mục .github/workflows. Đối với Strapi, chúng ta sẽ tạo file ci-cd.yml với ba job chính:
- lint: Kiểm tra định dạng code bằng ESLint.
- test: Chạy các unit test và integration test.
- build-and-deploy: Build Docker image, push lên Docker Hub và triển khai lên VPS.
Job lint
Job này sử dụng Node.js 18, cài đặt các phụ thuộc và chạy lệnh npm run lint. Nếu lint thất bại, pipeline sẽ dừng lại.
Job test
Đối với Strapi, chúng ta có thể viết các test bằng Jest kết hợp supertest để kiểm tra các endpoint API. Dưới đây là một ví dụ test đơn giản cho route /api/articles:
const request = require('supertest');
const strapi = require('../../src'); // Giả sử Strapi được khởi tạo ở đây
describe('GET /api/articles', () => {
it('should return a list of articles', async () => {
const res = await request(strapi).get('/api/articles');
expect(res.statusCode).toBe(200);
expect(Array.isArray(res.body.data)).toBe(true);
});
});Đảm bảo file jest.config.js đã cấu hình môi trường test phù hợp, ví dụ:
module.exports = {
testEnvironment: 'node',
roots: ['/tests'],
coverageDirectory: '/coverage',
};Job build-and-deploy
Job này thực hiện các bước:
- Login vào Docker Hub bằng secret
DOCKERHUB_TOKEN. - Build Docker image với tag dựa trên commit SHA.
- Push image lên Docker Hub.
- SSH vào VPS và thực thi
docker-compose pull && docker-compose up -dđể cập nhật container.
Dưới đây là nội dung file ci-cd.yml (đã được rút gọn để tập trung vào các bước quan trọng):
name: CI/CD for Strapi
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm run lint
test:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm test
build-and-deploy:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/strapi:${{ github.sha }}
- name: Deploy to VPS
env:
SSH_PRIVATE_KEY: ${{ secrets.VPS_SSH_KEY }}
VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_USER: ${{ secrets.VPS_USER }}
run: |
echo "$SSH_PRIVATE_KEY" > key.pem
chmod 600 key.pem
ssh -o StrictHostKeyChecking=no -i key.pem $VPS_USER@$VPS_HOST <<'EOF'
cd /opt/strapi
docker-compose pull
docker-compose up -d
EOFTrong ví dụ trên, chúng ta sử dụng các secret để bảo mật thông tin đăng nhập Docker Hub và SSH vào VPS. Các secret này cần được cấu hình trong phần Settings → Secrets của repository.
Triển khai lên môi trường Production
Docker Swarm hoặc Docker Compose trên VPS
Đối với các dự án nhỏ‑trung bình, Docker Compose vẫn là lựa chọn đơn giản và hiệu quả. Tuy nhiên, nếu dự án cần mở rộng quy mô, Docker Swarm hoặc Kubernetes sẽ cung cấp khả năng cân bằng tải và tự động phục hồi. Trong bài viết này, chúng ta sẽ tiếp tục sử dụng Docker Compose trên một VPS Ubuntu 22.04.
Cấu hình biến môi trường
Biến môi trường quan trọng bao gồm DATABASE_URL, HOST, PORT, ADMIN_JWT_SECRET, và JWT_SECRET. Để tránh rò rỉ thông tin, chúng ta sẽ lưu các biến này trong file .env.production và mount vào container.
# .env.production HOST=0.0.0.0 PORT=1337 DATABASE_CLIENT=postgres DATABASE_HOST=postgres DATABASE_PORT=5432 DATABASE_NAME=strapi_prod DATABASE_USERNAME=strapi_prod DATABASE_PASSWORD=StrongP@ssw0rd! ADMIN_JWT_SECRET=SuperSecretAdminKey JWT_SECRET=SuperSecretJwtKey
File docker-compose.yml cho production sẽ tham chiếu tới file env này:
version: "3.8"
services:
strapi:
image: yourdockerhubusername/strapi:${{ github.sha }}
env_file:
- .env.production
ports:
- "1337:1337"
depends_on:
- postgres
restart: always
postgres:
image: postgres:15-alpine
env_file:
- .env.production
volumes:
- pgdata_prod:/var/lib/postgresql/data
restart: always
volumes:
pgdata_prod:Quy trình cập nhật không downtime
Để tránh downtime khi cập nhật phiên bản mới, chúng ta có thể áp dụng chiến lược blue‑green deployment bằng cách chạy hai stack Docker Compose (blue và green) trên cùng một VPS, chuyển đổi Nginx proxy sau khi container mới đã sẵn sàng. Đây là một kỹ thuật nâng cao, nhưng rất hữu ích cho các hệ thống cần tính sẵn sàng 99.9%.
Best practices và lưu ý bảo mật
- Giữ Dockerfile tối thiểu: Sử dụng multi‑stage build để giảm kích thước image, loại bỏ các dev dependencies.
- Sử dụng .dockerignore để tránh copy các file không cần thiết (node_modules, .git, tests).
- Quản lý secret: Không bao giờ hard‑code mật khẩu trong Dockerfile hoặc compose. Dùng secret manager của cloud provider hoặc GitHub Secrets.
- Kiểm thử bảo mật: Thêm bước scan image bằng
TrivyhoặcAnchoretrong workflow để phát hiện lỗ hổng. - Giám sát logs: Đẩy logs của Strapi và PostgreSQL tới một hệ thống tập trung (ELK, Loki) để dễ dàng debug.
- Backup database: Thiết lập cron job sao lưu PostgreSQL và lưu trữ ở S3 hoặc Google Cloud Storage.
Kết luận
Việc thiết lập CI/CD cho Strapi CMS không chỉ giúp giảm thiểu lỗi phát sinh trong quá trình triển khai mà còn tạo ra một quy trình chuẩn hoá, dễ mở rộng cho các dự án Headless CMS trong tương lai. Khi áp dụng các best practices như multi‑stage Docker build, secret management và blue‑green deployment, bạn sẽ có một hệ thống ổn định, bảo mật và có khả năng đáp ứng nhanh các yêu cầu thay đổi của khách hàng. Để nắm bắt toàn bộ quy trình từ khởi tạo dự án, viết API, tới triển khai production một cách chi tiết, bạn có thể Tham khảo khóa học "Xây dựng Back-End Nodejs bằng Strapi CMS" tại đây.






