Xây Dựng CI/CD Pipeline Trên VPS TrumVPS: Tự Động Build, Test, Deploy — Tiết Kiệm 90% So Với GitHub Actions Hosted
Push code lên GitHub, vài phút sau website đã được deploy tự động — đó là sức mạnh của CI/CD pipeline. Nhưng GitHub Actions hosted runner có giới hạn: 2.000 phút/tháng miễn phí, hết là đứng. Self-hosted runner trên VPS TrumVPS cho bạn CI/CD không giới hạn, build nhanh hơn, cache hiệu quả hơn — chỉ từ 160.000đ/tháng.
Bài viết này hướng dẫn xây dựng CI/CD pipeline hoàn chỉnh trên VPS: từ Jenkins (enterprise-grade), GitHub Actions self-hosted runner (đơn giản nhất), đến Docker Registry riêng. Tự động build Docker image, chạy test, deploy lên production — tất cả trên 1 VPS.
🎯 Bài này dành cho: Developer muốn tự động hóa deploy, team startup cần CI/CD giá rẻ, freelancer muốn pipeline chuyên nghiệp, hoặc ai đang tốn tiền GitHub Actions/GitLab CI phút build.
Tại Sao CI/CD Self-Hosted Lại Rẻ Hơn?
| Giải pháp | Miễn phí | Trả phí | Chi phí cho team 5 người |
|---|---|---|---|
| GitHub Actions Hosted | 2.000 phút/tháng | $0.008/phút | ~$50-200/tháng |
| GitLab CI Hosted | 400 phút/tháng | $0.016/phút | ~$80-300/tháng |
| CircleCI | 6.000 phút/tháng | $15/seat + usage | ~$75-250/tháng |
| VPS Self-Hosted | - | 160.000đ/tháng | ~160.000đ/tháng (cố định!) |
Với VPS 4 vCPU 8GB RAM TrumVPS giá 320.000đ/tháng, bạn có thể chạy đồng thời Jenkins master, 3 GitHub Actions runners, Docker Registry, và monitoring — tất cả trong 1 VPS. Chi phí cố định, không giới hạn phút build, không phụ phí.
Cấu Hình VPS Cho CI/CD
| Quy mô | Cấu hình | Giá TrumVPS | Phù hợp |
|---|---|---|---|
| Starter | 4 vCPU, 4GB RAM, 80GB NVMe | 160.000đ | Cá nhân, 1-2 project |
| Standard | 4 vCPU, 8GB RAM, 120GB NVMe | 320.000đ | Team nhỏ 3-5 người |
| Professional | 8 vCPU, 16GB RAM, 200GB NVMe | 640.000đ | Team 5-15, Docker build nặng |
Phương Án 1: GitHub Actions Self-Hosted Runner (Đơn Giản Nhất)
Đây là cách nhanh nhất để có CI/CD không giới hạn. GitHub Actions self-hosted runner chạy workflow y hệt hosted runner, nhưng trên VPS của bạn.
Cài đặt Runner
Vào GitHub repo → Settings → Actions → Runners → New self-hosted runner. Chọn Linux x64, copy lệnh:
# Tạo user riêng cho runner
useradd -m -s /bin/bash github-runner
su - github-runner
# Download & extract
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64.tar.gz -L https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-linux-x64-2.321.0.tar.gz
tar xzf actions-runner-linux-x64.tar.gz
# Configure
./config.sh --url https://github.com/YOUR_USER/YOUR_REPO --token YOUR_TOKEN
# Install as service
sudo ./svc.sh install github-runner
sudo ./svc.sh start
Cài Docker & công cụ build
# Cài Docker
curl -fsSL https://get.docker.com | sh
usermod -aG docker github-runner
# Cài Node.js, Python, Go (tùy stack)
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs python3 python3-pip golang-go
Workflow CI/CD mẫu
# .github/workflows/deploy.yml
name: Deploy to VPS
on:
push:
branches: [main]
jobs:
test:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Run tests
run: npm test
build-and-deploy:
needs: test
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker tag myapp:${{ github.sha }} myapp:latest
- name: Deploy
run: |
docker compose down
docker compose up -d
- name: Health check
run: curl -f http://localhost:3000/health || exit 1
⚠️ Bảo mật Self-Hosted Runner: Chỉ dùng self-hosted runner cho private repository. Public repo mà bật self-hosted runner = ai cũng có thể chạy code trên VPS của bạn qua pull request. Luôn bật "Require approval for all outside collaborators" trong repo settings.
Phương Án 2: Jenkins — Enterprise CI/CD Server
Jenkins là CI/CD server mã nguồn mở mạnh nhất, hỗ trợ 1.800+ plugins. Phù hợp cho team cần pipeline phức tạp, multi-branch, approval gate.
Cài Jenkins với Docker
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
jenkins:
image: jenkins/jenkins:lts-jdk17
container_name: jenkins
user: root
ports:
- "8080:8080"
- "50000:50000"
volumes:
- jenkins_home:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
environment:
- JAVA_OPTS=-Xmx2048m
restart: unless-stopped
volumes:
jenkins_home:
EOF
docker compose up -d
Lấy password admin ban đầu:
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
Truy cập http://your-vps-ip:8080, nhập password, cài recommended plugins. Tạo admin user.
Jenkinsfile Pipeline Mẫu
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'localhost:5000'
APP_NAME = 'myapp'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Test') {
parallel {
stage('Unit Tests') {
steps {
sh 'npm ci'
sh 'npm test'
}
}
stage('Lint') {
steps {
sh 'npm run lint'
}
}
}
}
stage('Build Docker') {
steps {
sh "docker build -t ${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} ."
sh "docker tag ${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} ${DOCKER_REGISTRY}/${APP_NAME}:latest"
}
}
stage('Push to Registry') {
steps {
sh "docker push ${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER}"
sh "docker push ${DOCKER_REGISTRY}/${APP_NAME}:latest"
}
}
stage('Deploy to Staging') {
steps {
sh '''
ssh deploy@staging-server << 'DEPLOY'
docker pull localhost:5000/myapp:latest
cd /opt/myapp && docker compose up -d
DEPLOY
'''
}
}
stage('Approval') {
when { branch 'main' }
input {
message "Deploy to Production?"
ok "Deploy"
}
}
stage('Deploy to Production') {
when { branch 'main' }
steps {
sh '''
ssh deploy@prod-server << 'DEPLOY'
docker pull localhost:5000/myapp:latest
cd /opt/myapp && docker compose up -d
DEPLOY
'''
}
}
}
post {
success {
echo "Pipeline succeeded!"
}
failure {
echo "Pipeline failed!"
}
}
}
Phương Án 3: Docker Registry Riêng
Thay vì push image lên Docker Hub (rate limit 100 pulls/6h free), tự host registry riêng trên VPS:
# Tạo registry với basic auth
mkdir -p /opt/registry/{data,auth}
docker run -d \
--name registry \
--restart=unless-stopped \
-v /opt/registry/data:/var/lib/registry \
-v /opt/registry/auth:/auth \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-p 5000:5000 \
registry:2
# Tạo user
docker run --rm --entrypoint htpasswd httpd:2 -Bbn admin yourpassword > /opt/registry/auth/htpasswd
docker restart registry
# Test push
docker tag myapp:latest localhost:5000/myapp:latest
docker push localhost:5000/myapp:latest
🔐 Production Tip: Đặt Nginx reverse proxy với SSL trước registry, dùng Let's Encrypt. Cấu hình registry storage backend là S3 (MinIO) để lưu image bền vững, không mất khi rebuild VPS.
Best Practices CI/CD Trên VPS
1. Cache thông minh
- Docker layer caching: Dùng
docker build --cache-fromvà BuildKit để tận dụng cache giữa các build - Node.js: Cache
node_modulesvới actions/cache, hoặc dùngnpm cithay vìnpm install - Dependencies: Dùng local npm registry proxy (Verdaccio) để cache packages
# Docker BuildKit + cache
docker buildx build \
--cache-from type=local,src=/tmp/docker-cache \
--cache-to type=local,dest=/tmp/docker-cache,mode=max \
-t myapp:latest .
2. Parallel Jobs
Tận dụng multi-core VPS để chạy test, lint, build song song. Jenkins pipeline hỗ trợ parallel stage. GitHub Actions dùng matrix strategy.
3. Monitoring & Alerts
# Theo dõi disk usage — CI/CD nhanh đầy disk vì Docker images, cache
df -h /var/lib/docker
docker system prune -af --filter "until=72h"
# Cron dọn dẹp
echo "0 3 * * 0 docker system prune -af --filter \"until=168h\"" >> /etc/crontab
4. Zero-Downtime Deploy
# Blue-green deploy với Docker Compose
# Giữ container cũ chạy, start container mới trên port khác, switch Nginx upstream
docker compose -f docker-compose.green.yml up -d
# Health check green
curl -f http://localhost:3001/health
# Switch traffic
sed -i 's/:3000/:3001/' /etc/nginx/sites-enabled/app
nginx -s reload
# Tắt blue sau 5p grace period
sleep 300 && docker compose -f docker-compose.blue.yml down
So Sánh CI/CD Tools
| Tool | Độ khó cài | RAM | Phù hợp | Điểm mạnh |
|---|---|---|---|---|
| GitHub Actions Runner | ⭐ Dễ | 1-2GB | Cá nhân, team nhỏ | Setup 5 phút, dùng workflow sẵn |
| Jenkins | ⭐⭐⭐ Trung bình | 2-4GB | Team vừa-lớn | 1.800+ plugins, pipeline as code |
| Drone CI | ⭐⭐ Trung bình | 512MB-1GB | Team nhỏ | Nhẹ, native Docker, YAML config |
| Woodpecker CI | ⭐⭐ Trung bình | 256-512MB | Cá nhân | Fork Drone, siêu nhẹ, open source |
| Gitea Actions | ⭐⭐ Trung bình | 1-2GB | Team tự host Git | Tương thích GitHub Actions syntax |
Security Hardening
1. Không chạy runner/pipeline với quyền root
# Tạo user riêng cho CI/CD
useradd -m -s /bin/bash cicd
usermod -aG docker cicd # Chỉ thêm docker group, không sudo
2. Secret management
# Dùng GitHub Secrets / Jenkins Credentials, không hardcode
# Jenkinsfile:
withCredentials([string(credentialsId: 'DOCKER_HUB_PASSWORD', variable: 'DOCKER_PASS')]) {
sh "docker login -u myuser -p ${DOCKER_PASS}"
}
# Hoặc HashiCorp Vault trên VPS riêng
docker run -d --cap-add=IPC_LOCK \
-e 'VAULT_DEV_ROOT_TOKEN_ID=myroot' \
-p 8200:8200 vault
3. Network isolation
# Jenkins/runner trong Docker network riêng
docker network create cicd-net
# Registry cũng trong network riêng, không expose port ra ngoài
# Chỉ Jenkins và production server mới access được
4. Audit logging
# Log mọi deploy ra file
echo "$(date): Deploy ${APP_NAME} version ${BUILD_NUMBER} by ${GITHUB_ACTOR}" >> /var/log/deployments.log
Case Study: Pipeline Cho Website WordPress
Một quy trình CI/CD thực tế cho website WordPress trên VPS TrumVPS:
# .github/workflows/deploy-wp.yml
name: Deploy WordPress Site
on:
push:
branches: [main]
paths:
- 'wp-content/themes/**'
- 'wp-content/plugins/**'
jobs:
deploy:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Lint PHP
run: |
docker run --rm -v $PWD:/app php:8.2-cli \
find /app/wp-content -name '*.php' -exec php -l {} \;
- name: Backup current site
run: |
ssh ${{ secrets.VPS_HOST }} "
cd /var/www/html
tar czf /backups/wp-backup-$(date +%Y%m%d-%H%M).tar.gz .
"
- name: Rsync deploy
run: |
rsync -avz --delete \
--exclude='wp-config.php' \
--exclude='wp-content/uploads/' \
--exclude='.git/' \
wp-content/themes/ \
${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }}:/var/www/html/wp-content/themes/
- name: Clear cache
run: |
ssh ${{ secrets.VPS_HOST }} "
wp cache flush --allow-root
wp rewrite flush --allow-root
"
- name: Health check
run: curl -f -o /dev/null -s https://example.com || exit 1
- name: Notify Telegram
if: always()
run: |
STATUS="${{ job.status }}"
curl -s "https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
-d "text=Deploy ${STATUS}: $(date)"
🚀 Tự Động Hóa Deploy Ngay Hôm Nay
VPS TrumVPS từ 80K/tháng, cấu hình mạnh mẽ cho CI/CD pipeline. Build không giới hạn, không phụ phí, deploy trong tích tắc.
Đăng Ký VPS TrumVPS Ngay →Bài Viết Liên Quan
- Hướng Dẫn Docker & Docker Compose Từ A-Z Trên VPS
- Cài Docker Portainer — Quản Lý Container Bằng Web GUI
- Load Balancer HAProxy + Nginx — Zero-Downtime Deploy
- Hướng Dẫn Deploy Website Lên VPS Cho Người Mới
- Git Deploy Tự Động — Push Code Là Deploy
- DevOps Workflow Trên VPS: Từ Dev Đến Production
- Cài MinIO S3 Object Storage — Registry Backend Cho Docker