使用自己的伺服器部署服務的情況下,利用GitHub做CI/CD有很多方式,例如使用定期執行每隔一段時間就上GitHub看有沒有新版本要編譯部署、透過Webhooks由GitHub通知伺服器上來拿最新的程式碼編譯部署,本文使用GitHub Actions,在儲存庫更新、收到Pull Request等情況下由GitHub Actions自動執行Workflow,自動打包原始碼、編譯、上傳到遠端伺服器、呼叫遠端腳本重啟服務。
自動化部署
本教學為在Linux Debian環境下,部署一個透過bash script控制systemd啟動Java spring boot專案,實務上可以在腳本中換成python、nodejs、docker等任何方案。

Linux基本環境
這邊是在Linux上要先做的基本操作,讓GitHub可以以最小權限原則SSH進到我們伺服器。
建立帳號
登入到測試環境的Linux上,透過以下指令建立Actions使用的帳號 github-actions,可以修改成其他名稱,但是後續教學中的指令全部要自行更改成一樣的名稱。
sudo adduser --disabled-password --gecos "" github-actions
建立SSH Key
首先要建立一組SSH Key,可以在自己的電腦上,或是有ssh環境的電腦上執行以下操作,或是透過putty等圖形界面軟體建立SSH Key。
ssh-keygen -t ed25519 -C "github-actions-deploy" -f id_ed25519_github
執行完會在~/.ssh/底下得到兩個檔案:
id_ed25519_github(私鑰):絕對不可外流!這是給 GitHub Actions 拿在手上的。id_ed25519_github.pub(公鑰):這是給伺服器看的。
回到遠端的Linux伺服器上。
# 切換到github-actions帳號
sudo -u github-actions -i
# 建立SSH Key登入設定
mkdir ~/.ssh
chmod 700 .ssh
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
# 將剛才的公鑰內容填入
echo '這邊填入你的公鑰內容' >> ~/.ssh/authorized_keys
安全強化:限制 SSH 執行範圍(可選)
為了更安全,可以限制這個 SSH Key 只能執行特定的行為。在 .ssh/authorized_keys 中,在公鑰前面加上限制。
no-port-forwarding,no-agent-forwarding,no-x11-forwarding,no-pty ssh-rsa AAAAB... (公鑰)
如果佈署指令需要用到 pty (虛擬終端),則不要加 no-pty。
服務運行環境
這邊是每建立一個新服務,就要在Linux上做一次的事情。本文中範例的專案名稱叫做 klabtw,因為是測試環境,所以這個服務我稱為 klabtw-test。
建立工作目錄
sudo mkdir -p /opt/klabtw-test/{app,cache}
建立啟動腳本
建立一個檔案 /opt/klabtw-test/start-service.sh 填寫以下內容,如果不是使用Java,也可以自行換成其他服務。
#!/bin/bash
APP_NAME="klabtw-test"
WORK_DIR="/opt/$APP_NAME"
# 這邊填入你想指定的Java vm args
JAVA_OPTS="-Xmx1024m -Xms1024m \
-Dspring.profiles.active=test"
# 使用exec執行,避免程式脫離Systemd控制
exec /usr/bin/java $JAVA_OPTS -jar $WORK_DIR/app/$APP_NAME.jar
建立服務單元
使用root權限建立檔案 /etc/systemd/system/klabtw-test.service 。
[Unit]
Description=Klabtw Service (test)
After=syslog.target
[Service]
Restart=always
RestartSec=10
User=github-actions
Group=github-actions
WorkingDirectory=/opt/klabtw-test
ExecStart=/bin/bash /opt/klabtw-test/start-service.sh
[Install]
WantedBy=multi-user.target
建立部署與重啟腳本
編輯 /opt/klabtw-test/deploy-and-restart.sh 檔案。
#!/bin/bash
APP_NAME="klabtw-test"
# 定義路徑
BASE_DIR="/opt/$APP_NAME"
CACHE_JAR="$BASE_DIR/cache/$APP_NAME.jar"
APP_JAR="$BASE_DIR/app/$APP_NAME.jar"
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 開始執行部署流程..."
# 1. 檢查快取檔案是否存在
if [ ! -f "$CACHE_JAR" ]; then
echo "錯誤: 找不到快取檔案 $CACHE_JAR"
exit 1
fi
# 2. 搬移檔案
echo "正在搬移 JAR 檔至 app 目錄..."
mv "$CACHE_JAR" "$APP_JAR"
# 3. 重啟 Systemd 服務
echo "正在重啟 $APP_NAME 服務..."
sudo systemctl restart $APP_NAME
# 4. 驗證結果
sleep 2 # 給服務一點反應時間
if systemctl is-active --quiet $APP_NAME; then
echo "部署成功!服務目前狀態:ACTIVE"
systemctl status $APP_NAME --no-pager
else
echo "部署失敗!請檢查日誌"
exit 1
fi
設定Sudo免密碼權限
自動化腳本需要重啟 Spring 服務(或是你指定的其他服務),但我們不希望給這個帳號完整的 root 權限,也不希望它在執行systemctl時被要求輸入密碼。
sudo visudo -f /etc/sudoers.d/github-actions
輸入以下內容,然後按下 CTRL + X 再按下 Y 存檔。
# 允許 github-actions 使用者免密碼重啟特定的 systemd 服務
github-actions ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart klabtw-test, /usr/bin/systemctl status klabtw-test
依照需求可以加上以下指令,每個指令用半形逗號隔開。
- /usr/bin/systemctl start klabtw-test
- /usr/bin/systemctl stop klabtw-test
Github Actions環境
建立Repository
首先要有一個GitHub的儲存庫,放在個人或是組織底下都可以,程式碼跟Actions必須用同一個儲存庫,所以已經在GitHub進行版本控制的人可以直接進下一步。
設定Secrets
這一步要設定GitHub
進入我們建立好、或是已經在使用的儲存庫點選 Settings 頁籤,然後左側導覽列選擇 Secrets and variables 目錄,展開後點選 Actions。然後在 Repository secrets 那邊點選 New secret,重複三次新增以下三組資料。
| Name | Secret | 說明 |
|---|---|---|
| REMOTE_HOST | 0.0.0.0 | 你的Linux伺服器IP |
| REMOTE_USER | github-actions | 給GitHub Actions登入的帳號 |
| SSH_PRIVATE_KEY | —–BEGIN OPENSSH PRIVATE KEY—– abcd… —–END OPENSSH PRIVATE KEY—– | id_ed25519_github (私鑰) |
填寫Workflow
在我的需求中希望branch main更新就自動部署測試機,或者有不是Draft的PR更新也要自動部署。可以依照需求改成「只有main更新才觸發」、「Draft PR也要觸發」、「只有包含tag的main更新才要觸發(適合正式環境)」等各種功能,這種細項的調整沒辦法一個個列出來,所以我這邊提供基本概念說明GitHub Actions可以做什麼事情,細節可以交給AI幫忙寫或是修改。
name: Build and Deploy Spring App
on:
push:
branches:
- main
pull_request:
# 必須包含 ready_for_review,按按鈕時才會啟動
types: [opened, synchronize, reopened, ready_for_review]
jobs:
build-and-deploy:
# 核心邏輯:如果是直接 push 到 main,或是 PR 狀態「不是」草稿
if: |
github.event_name == 'push' ||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'gradle'
- name: Build with Gradle
run: |
chmod +x gradlew
./gradlew clean bootJar
- name: Rename JAR
run: |
# 尋找產出的 jar 並統一命名,方便後續腳本抓取
mv build/libs/*.jar build/libs/klabtw-test.jar
- name: SCP to Debian Server
uses: appleboy/[email protected]
with:
host: ${{ secrets.REMOTE_HOST }}
username: ${{ secrets.REMOTE_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "build/libs/klabtw-test.jar"
target: "/opt/klabtw-test/cache"
strip_components: 2
- name: Execute Remote Deploy Script
uses: appleboy/[email protected]
with:
host: ${{ secrets.REMOTE_HOST }}
username: ${{ secrets.REMOTE_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
# 這裡只執行一支腳本
script: bash /opt/klabtw-test/deploy-and-restart.sh
測試
完成以上操作就可以在GitHub的Repository的Actions頁籤看到自動執行Workflow的進度與歷史訊息了,我們在 deploy-and-restart.sh 輸出的訊息也可以在這邊看見,如果執行失敗也可以點進去觀看原因。