
架構概覽
flowchart LR Feature[Feature Branch<br/>功能開發] -->|Merge Request| Develop[develop 分支<br/>整合測試] Develop -->|合併| Main[main 分支] Main -->|git tag v1.x.0| CI[CI Pipeline<br/>build + test] CI -->|deploy| Staging[Staging 環境<br/>QA 驗證] Staging -->|手動確認| Prod[Prod 環境<br/>正式上線] Main -->|緊急修復| Hotfix[Hotfix 分支] Hotfix -->|合回| Main Hotfix -->|同步| Develop
Git & CI:讓每一次交付都可追蹤、可回滾
Git 不只是版本控制工具,它同時也是 CD 的觸發源、Release 的版本紀錄、以及 Rollback 的回復依據。這篇文章定義分支策略、Release 流程、以及出事時怎麼快速回滾,讓團隊不會因為「不確定上了什麼版本」而慌張。
架構概覽
gitGraph commit id: "init" branch develop commit id: "feat-A" commit id: "feat-B" checkout main merge develop id: "v1.0.0" tag: "v1.0.0" branch hotfix commit id: "fix-critical" checkout main merge hotfix id: "v1.0.1" tag: "v1.0.1" checkout develop merge hotfix commit id: "feat-C" checkout main merge develop id: "v1.1.0" tag: "v1.1.0"
主要分支:main(穩定版本)、develop(開發中)。功能在 develop 開發,合併到 main 時打 tag 觸發 Release。緊急修復走 hotfix 分支,修完同時合回 main 和 develop。
核心概念
-
分支策略:採用簡化版的 Git Flow。
main永遠是可部署的穩定版本,develop是下一個版本的開發分支。功能開發從develop開 feature branch,完成後 merge 回develop。這樣做比直接在main上開發安全,因為main始終對應到正在運行的版本,不會有半完成的功能。 -
Semantic Versioning:版本號用
v{major}.{minor}.{patch}格式。major代表破壞性變更(API 不相容)、minor代表新功能、patch代表 bug 修復。這讓任何人看到版本號就知道這次更新的影響範圍。例如v1.0.0→v1.0.1代表只有 bug fix,可以安心升級。 -
Release 流程:從
develop合併到main,在main上打 tag(v1.1.0),CI 自動觸發 build → deploy to staging。QA 驗證通過後,手動觸發 deploy to prod。每個 Release 都在 GitLab 建立 Release note,記錄包含哪些 commit 和功能。 -
Rollback 機制:如果新版本出問題,有兩種回滾方式。快速回滾:在 Portainer 把 Docker image tag 退回前一個版本(例如
v1.0.0),幾秒內生效。完整回滾:從main的前一個 tag checkout,重新 build 並部署。前者適合緊急情況,後者適合需要包含 config 變更的回滾。
使用情境
-
正常 Release:開發團隊完成了 3 個新功能,在
develop上都通過測試。PM 確認要上線,將developmerge 到main,打 tagv1.2.0。CI 自動 build、push image、deploy to staging。QA 花一天驗證,沒問題後手動觸發 prod deploy。 -
緊急 Hotfix:prod 上發現 critical bug。從
main的最新 tag 開hotfix/fix-payment分支,修完後 merge 回main,打 tagv1.2.1。CI 走加速流程(跳過 staging,直接 deploy prod)。修完後也要 merge 回develop,避免下次 release 又把 bug 帶回來。 -
Rollback 情境:
v1.3.0上線後發現效能嚴重下降。立即在 Portainer 把 image tag 從v1.3.0退回v1.2.1,服務恢復正常。同時在develop上排查效能問題,修完後走正常 Release 流程重新上線。
實作範例 / 設定範例
Release 腳本
#!/bin/bash
# scripts/release.sh - 建立新 Release
set -euo pipefail
VERSION=$1 # e.g., v1.2.0
# 確認在 main 分支
CURRENT_BRANCH=$(git branch --show-current)
if [ "$CURRENT_BRANCH" != "main" ]; then
echo "Error: must be on main branch"
exit 1
fi
# 確認沒有未 commit 的變更
if [ -n "$(git status --porcelain)" ]; then
echo "Error: uncommitted changes"
exit 1
fi
# 建立 annotated tag
git tag -a "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"
echo "Release $VERSION created and pushed."
echo "CI will automatically build and deploy to staging."Rollback 操作手冊
# === 快速回滾(Docker image 層級)===
# 1. 確認要回滾到的版本
docker images | grep myapp-api
# 2. 在 docker-compose.yml 修改 image tag
# image: registry.example.com/myapp-api:v1.3.0
# → image: registry.example.com/myapp-api:v1.2.1
# 3. 重新部署
docker compose pull && docker compose up -d
# 4. 驗證
curl -sf https://api.example.com/health
# === 完整回滾(包含 config 變更)===
# 1. 找到要回滾的 commit
git log --oneline --tags
# 2. 建立 revert branch
git checkout -b revert/v1.3.0 v1.2.1
# 3. 推送並觸發 CI
git push origin revert/v1.3.0
# 4. 建立 MR merge 回 mainGitLab Release Note 模板
## v1.2.0 Release Notes
### New Features
- [EC-123] 新增購物車批次刪除功能
- [EC-124] 支援多種付款方式切換
### Bug Fixes
- [EC-125] 修正訂單金額計算錯誤
### Breaking Changes
- 無
### Migration Steps
- 無需額外步驟
### Rollback Plan
- Docker image rollback to v1.1.0
- 無 DB migration 需要回滾常見問題與風險
-
直接在 main 上開發:跳過 develop 直接在 main 提交,導致 main 上有未完成的功能。如果這時需要 hotfix,就會把半成品一起上線。避免方式:設定 GitLab 的 branch protection,禁止直接 push 到 main。
-
Tag 忘記打:deploy 了但沒有打 tag,之後要 rollback 時找不到對應的版本。避免方式:CI pipeline 強制要求 deploy 必須有 tag,沒有 tag 的 commit 不允許 deploy 到 staging/prod。
-
Hotfix 沒有 merge 回 develop:hotfix 只合回 main,忘記也合回 develop。結果下次 release 時,修好的 bug 又出現了。避免方式:hotfix PR 的 checklist 加上「已 merge 回 develop」。
-
Rollback 後資料庫不相容:新版本做了 DB migration(加了 column),rollback 到舊版本後,舊版的程式碼不認識新 column,可能出錯。避免方式:DB migration 要設計成向前相容(additive only),不要在 migration 中刪 column 或改 column type。
優點
- 每個版本有明確的 tag 和 release note,方便追蹤
- Rollback 機制明確,出事時不慌張
- 分支策略清楚,多人協作不衝突
缺點 / 限制
- Git Flow 對小團隊可能太重(可以簡化為 trunk-based development)
- Hotfix 流程比較繁瑣(要 merge 到多個分支)
- DB migration 的回滾比程式碼回滾複雜很多