Tự Động Deploy Lên VPS Bằng GitHub Actions: Quy Trình CI/CD An Toàn

Thiết lập GitHub Actions để tự động deploy ứng dụng lên VPS qua SSH, quản lý secrets, environment và rollback an toàn khi release production.

Khi mỗi lần cập nhật ứng dụng đều phải SSH vào VPS, kéo code, cài dependency và restart service thủ công, rủi ro sai sót tăng dần theo số lần release. GitHub Actions giúp tự động hóa chuỗi build, test và deploy từ repository tới VPS. Bài viết này hướng dẫn một mô hình CI/CD đơn giản nhưng an toàn: dùng SSH key riêng cho deployment, secrets, environment protection, concurrency và script deploy có thể rollback.

Điểm quan trọng Hãy lựa chọn dịch vụ dựa trên nhu cầu sử dụng thực tế và theo dõi tài nguyên thường xuyên để có hiệu quả tốt nhất.

Khi dự án còn nhỏ, cách deploy quen thuộc thường là:

  1. SSH vào VPS;
  2. git pull;
  3. cài dependency;
  4. build;
  5. restart service.

Cách này hoạt động, nhưng càng deploy nhiều càng dễ gặp lỗi do thao tác thủ công, deploy chồng nhau hoặc quên một bước. GitHub Actions có thể tự động hóa quy trình này khi code được push lên branch phù hợp.

Mục tiêu của bài viết là xây dựng một pipeline đơn giản, dễ hiểu và có kiểm soát, không cố biến một VPS thành nền tảng orchestration phức tạp.

Kiến trúc CI/CD cơ bản

Một luồng phổ biến:

Developer push code
        ↓
GitHub Actions
        ↓
Build / Test
        ↓
SSH tới VPS
        ↓
Deploy release mới
        ↓
Restart service + health check

GitHub Actions chạy workflow dựa trên event như push, pull_request hoặc workflow_dispatch.

Khi nào nên tự động deploy bằng GitHub Actions?

Phù hợp khi:

  • ứng dụng chạy trên một hoặc vài VPS;
  • team đã lưu code trên GitHub;
  • muốn giảm thao tác SSH thủ công;
  • cần lịch sử deployment rõ ràng;
  • muốn chạy test trước deploy;
  • cần tách staging và production.

Nếu bạn đang quản lý nhiều VPS và muốn chuẩn hóa cấu hình toàn hệ thống, Ansible có thể bổ trợ tốt. Xem bài Quản lý nhiều VPS bằng Ansible.

Chuẩn bị user deploy riêng trên VPS

Không nên deploy bằng root nếu không cần.

Tạo user:

sudo adduser deploy

Cấp quyền đúng nhu cầu. Ví dụ nếu app nằm trong:

/var/www/myapp

hãy đảm bảo user deploy có quyền với thư mục đó mà không cần quyền rộng toàn hệ thống.

Nếu service cần restart bằng systemd, có thể cấu hình sudoers rất hẹp thay vì cho quyền sudo toàn phần.

Ví dụ dùng visudo:

sudo visudo

Rule minh họa:

deploy ALL=NOPASSWD:/bin/systemctl restart myapp

Chỉ thêm command thực sự cần thiết.

Tạo SSH key riêng cho CI/CD

Trên máy quản trị:

ssh-keygen -t ed25519 -C "github-actions-deploy" -f github-actions-deploy

Bạn sẽ có:

  • github-actions-deploy — private key;
  • github-actions-deploy.pub — public key.

Copy public key vào:

/home/deploy/.ssh/authorized_keys

Kiểm tra permission:

chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh

Không dùng private key cá nhân chính của bạn cho CI/CD.

Lưu secrets trong GitHub

Trong repository, vào phần Actions secrets hoặc Environment secrets và tạo các biến như:

  • VPS_HOST;
  • VPS_USER;
  • VPS_SSH_KEY;
  • VPS_PORT nếu không dùng 22.

VPS_SSH_KEY chứa private key deployment.

GitHub khuyến nghị dùng secrets cho thông tin nhạy cảm và áp dụng least privilege.

Dùng Environment cho production

GitHub Environments giúp tách:

  • staging;
  • production;
  • secrets theo môi trường;
  • deployment rules.

Ví dụ job:

environment: production

Tùy plan và loại repository, bạn có thể dùng required reviewers hoặc branch restrictions.

Điểm quan trọng là production secrets chỉ được dùng bởi job nhắm đúng environment.

Workflow đầu tiên

Tạo file:

.github/workflows/deploy.yml

Ví dụ cơ bản:

name: Deploy to VPS

on:
  push:
    branches:
      - main
  workflow_dispatch:

concurrency:
  group: production-deploy
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Run tests
        run: |
          echo "Run your test command here"

      - name: Configure SSH
        env:
          SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
          VPS_HOST: ${{ secrets.VPS_HOST }}
        run: |
          mkdir -p ~/.ssh
          chmod 700 ~/.ssh
          printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts

      - name: Deploy
        env:
          VPS_HOST: ${{ secrets.VPS_HOST }}
          VPS_USER: ${{ secrets.VPS_USER }}
        run: |
          ssh "$VPS_USER@$VPS_HOST" '/opt/myapp/deploy.sh'

Vì sao cần known_hosts?

SSH cần xác minh host key để chống kết nối nhầm hoặc man-in-the-middle.

ssh-keyscan tiện cho automation nhưng nếu security yêu cầu cao, nên xác minh fingerprint trước và lưu giá trị tin cậy thay vì chấp nhận bất kỳ host key nào tại runtime.

Một cách chặt chẽ hơn là lưu fingerprint/known_hosts đã xác minh thành secret hoặc repository variable phù hợp.

Tách logic deploy khỏi workflow

Thay vì nhét toàn bộ command vào YAML, nên có script trên VPS:

#!/usr/bin/env bash
set -euo pipefail

APP_DIR="/var/www/myapp"
cd "$APP_DIR"

git fetch origin main
git reset --hard origin/main

npm ci
npm run build

sudo systemctl restart myapp

Lưu tại:

/opt/myapp/deploy.sh

Cấp quyền:

sudo chmod 750 /opt/myapp/deploy.sh
sudo chown deploy:deploy /opt/myapp/deploy.sh

set -euo pipefail giúp script dừng khi gặp lỗi thay vì tiếp tục trong trạng thái không rõ ràng.

Nhưng git reset --hard có nguy hiểm không?

Có, nếu bạn lưu file chỉnh tay trong working tree.

Với production, repository nên được xem là immutable source. File config/secrets nên nằm ngoài Git hoặc được inject qua environment/config management.

Nếu bạn có dữ liệu người dùng trong thư mục project, tuyệt đối không để lệnh deploy xóa nhầm.

Mô hình release directory an toàn hơn

Thay vì deploy trực tiếp vào một thư mục, có thể dùng:

/var/www/myapp/
├── releases/
│   ├── 20260928-090100/
│   ├── 20260928-103000/
│   └── 20260928-121500/
├── current -> releases/20260928-121500
└── shared/

Mỗi lần deploy:

  1. tạo release mới;
  2. build trong release;
  3. link config/shared data;
  4. health check;
  5. đổi symlink current;
  6. reload service.

Nếu lỗi, rollback bằng cách đổi symlink về release trước.

Health check sau deploy

Workflow không nên coi deploy thành công chỉ vì SSH command trả exit code 0.

Ví dụ:

curl --fail --silent --show-error https://example.com/health

Nếu API trả non-2xx, command sẽ fail.

Bạn cũng có thể kiểm tra local service:

curl --fail http://127.0.0.1:3000/health

Deploy Docker Compose

Nếu app dùng Docker Compose:

#!/usr/bin/env bash
set -euo pipefail
cd /opt/myapp

git fetch origin main
git reset --hard origin/main

docker compose pull
docker compose up -d --build

docker compose ps

Không chạy docker compose down nếu không cần, vì có thể tạo downtime không cần thiết.

Nếu bạn mới dùng Docker, xem Cài Docker và Docker Compose trên VPS Ubuntu.

Không đưa secrets vào repository

Tránh commit:

.env
private.key
production.json

Dùng:

  • GitHub secrets;
  • Environment secrets;
  • secret manager;
  • file .env được quản lý riêng trên VPS.

Không echo secret ra log để debug.

Giới hạn quyền của workflow

GitHub khuyến nghị cấp tối thiểu quyền cho GITHUB_TOKEN.

Ví dụ:

permissions:
  contents: read

Nếu workflow không cần ghi repository, đừng cấp write permission.

Tránh deploy chồng nhau bằng concurrency

Nếu hai commit được push liên tiếp, hai workflow có thể cùng deploy.

Dùng:

concurrency:
  group: production-deploy
  cancel-in-progress: false

Giúp tại một thời điểm chỉ có một deployment production đang chạy.

Tách staging và production

Một chiến lược tốt:

  • branch develop → staging;
  • branch main → production;
  • production dùng environment riêng;
  • test phải pass trước deploy;
  • manual approval nếu cần.

Đừng dùng chung một private key và một set secrets cho mọi môi trường nếu không cần.

Rollback như thế nào?

Một pipeline không có rollback chưa hoàn chỉnh.

Có thể rollback bằng:

  • release directory + symlink;
  • image Docker version cũ;
  • Git tag/release đã xác nhận;
  • restore database nếu migration không tương thích.

Database migration là phần khó nhất vì rollback code chưa chắc rollback schema được.

Với migration quan trọng, nên:

  1. backup;
  2. dùng migration backward-compatible;
  3. deploy app;
  4. kiểm tra;
  5. chỉ xóa column/schema cũ ở release sau.

Sai lầm thường gặp

Dùng root SSH key trong GitHub Secrets

Blast radius quá lớn. Dùng deploy user riêng và quyền tối thiểu.

Tắt host key checking

StrictHostKeyChecking=no tiện nhưng làm yếu xác minh SSH. Nên quản lý known_hosts đúng cách.

Deploy trực tiếp khi test chưa chạy

Pipeline nên fail sớm trước khi chạm production.

Không có concurrency

Hai deployment song song có thể ghi đè file hoặc restart service chồng nhau.

Không có health check

Workflow xanh không có nghĩa ứng dụng thật sự phục vụ được traffic.

Checklist CI/CD an toàn

  • User deploy riêng.
  • SSH key riêng cho automation.
  • Secrets không commit vào Git.
  • GITHUB_TOKEN least privilege.
  • Production environment riêng.
  • Test trước deploy.
  • Có concurrency.
  • Host key được xác minh.
  • Deploy script dùng set -euo pipefail.
  • Có health check.
  • Có rollback plan.
  • Có backup trước migration lớn.

FAQ

GitHub Actions có deploy được lên VPS không?

Có. Một mô hình phổ biến là workflow chạy test, sau đó SSH tới VPS bằng deploy key và gọi script triển khai.

Có nên lưu SSH private key trong GitHub Secrets?

Có thể, nếu key đó dành riêng cho deployment, giới hạn quyền và được xoay vòng định kỳ. Không dùng private key cá nhân chính.

Nên deploy bằng git pull hay build artifact?

Project nhỏ có thể dùng Git trên server. Hệ thống nghiêm ngặt hơn thường build artifact/image trước rồi deploy immutable artifact để dễ kiểm soát và rollback.

Có cần manual approval trước production không?

Tùy mức rủi ro. Với hệ thống quan trọng, environment approval giúp giảm deploy nhầm.

CI/CD có thay Ansible không?

Không. GitHub Actions điều phối pipeline, còn Ansible phù hợp quản lý cấu hình nhiều máy. Hai công cụ có thể phối hợp.

Kết luận

GitHub Actions giúp biến quy trình deploy VPS từ thao tác thủ công thành pipeline có thể lặp lại, audit và kiểm soát.

Hãy bắt đầu nhỏ: test → SSH bằng deploy user → chạy script → health check. Sau đó mới mở rộng sang staging, environment protection, immutable artifact và rollback tự động.

Nếu bạn đang cần VPS để chạy môi trường staging hoặc production cho ứng dụng web, có thể tham khảo các dịch vụ VPS tại HUONG CLOUD và lựa chọn tài nguyên theo workload thực tế.

Nguồn tham khảo

Kết luận

Nếu chưa chắc cấu hình hoặc loại dịch vụ nào phù hợp, hãy gửi nhu cầu thực tế để đội ngũ hỗ trợ tư vấn trước khi mua.

Chưa biết nên chọn gói nào?

Xem bảng giá VPS hoặc liên hệ đội ngũ hỗ trợ để chọn cấu hình phù hợp.

Xem bảng giá VPS →