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.

1

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
2

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
3

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 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