那次漏掉一行指令,賠掉兩天對帳
幾年前接手一個電商後台,上線靠工程師 SSH 進去依序跑 git pull、composer install、migrate、清 cache。某次週五晚上趕改版漏跑 php artisan config:cache,站台繼續讀舊的金流設定,三小時內 27 筆訂單的付款回調全部落空。技術修復 40 分鐘,人工對帳花了兩天。結論不是誰不小心:當上線是靠記性執行的指令,任何人在疲勞的夜裡都會漏掉一步。
我們的做法:把上線變成沒人能手動介入的按鈕
ScriptWalker 的 production 沒有人握有手動部署權限,所有變更走同一條流水線。Laravel 專案從 git push 到上線平均 11 分鐘:CI 約 7 分鐘、部署與驗證約 4 分鐘。
git push(feature 分支)
→ GitHub Actions CI:Pint 格式檢查 → Larastan 靜態分析 → Pest 測試 → Playwright E2E(8 條關鍵路徑)
→ PR 全綠 + 1 位 reviewer 核可
→ squash 合併 main
→ Actions 產生版本 tag
→ 呼叫 Laravel Forge deploy hook(新版本 clone 進 releases/,symlink 切換)
→ migrate --force + artisan optimize
→ 對 /health 與 3 條關鍵路徑跑煙霧測試
→ 建立 Sentry release 並關聯 commit
→ Slack #deploys 回報;失敗自動把 symlink 指回上一版
Flutter 走平行支線:同一個 tag 觸發 Codemagic 建 iOS 與 Android,簽章後直送 TestFlight 與 Play 內部測試軌。
為什麼不做更「完整」的版本
三件常見做法,我們刻意不做。
- 沒有 Kubernetes、沒有真的 Blue-Green。專案多是單機或雙機規模,Laravel Forge 的 zero-downtime 部署用
releases/加 symlink 切換就能讓使用者無感;養一套 K8s 只會吃掉小團隊產能。 - E2E 只跑 8 條,不是 80 條。CI 時間預算上限是 10 分鐘;超過,工程師就會把 PR 堆著、一次合五個,那才是風險來源。Playwright 官方 CI 章節也建議用 sharding 分散。
- main 直上 production,金流與推播除外。帶
payment或push-notification標籤的 PR 會進 GitHub Environments 的 required reviewer 閘門。
取捨是:流程極短、關鍵路徑加鎖,而不是流程完整卻沒人走完。
實際怎麼跑:工具、設定與檔案
Step 1|本地攔截。pre-commit hook 只跑 Pint 與 dart format,兩秒結束、不跑測試——慢的 hook 就是會被跳過的 hook。
Step 2|CI。workflow 檔節錄:
name: ci
on:
pull_request:
push: { branches: [main] }
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
- run: composer install --no-interaction --prefer-dist
- run: ./vendor/bin/pint --test
- run: ./vendor/bin/phpstan analyse --memory-limit=1G
- run: php artisan test --parallel
e2e:
needs: quality
runs-on: ubuntu-latest
steps:
- run: npx playwright test --shard=1/2
concurrency 讓同分支的舊 run 自動取消,省時也省錢,細節見 GitHub Actions 官方文件。
Step 3|部署與驗證。Forge deploy script 的尾段固定是:
php artisan migrate --force
php artisan optimize
php artisan queue:restart
curl -fsS https://APP_URL/health || exit 1
sentry-cli releases new "$FORGE_SHA"
sentry-cli releases set-commits --auto "$FORGE_SHA"
artisan optimize 正是當年漏掉的那一步,Laravel 官方部署文件列為必跑;queue:restart 讓 worker 載入新程式碼;最後推進 Sentry Releases,錯誤就能對應到哪次部署。
Step 4|Flutter。依 Flutter 官方 CD 指南用 Fastlane 包裝簽章與上傳、交給 Codemagic 執行。
這個做法的代價(誠實版)
- CI 帳單是真的要付。十幾個 repo 同時跑,每月都超出免費額度,GitHub 方案頁寫明超出按分鐘計費,我們自己吸收。
- 改一個錯字也要 11 分鐘。沒有例外通道,客戶通常很難理解。
- pipeline 本身要養。每季約 4 到 6 小時處理 flaky E2E、更新 action 版本、修升版後炸掉的 job,這些工時不進報價單。
- 新人前兩週會卡住。第一支 PR 常常紅三四次才變綠。
什麼情況不該這樣做
- 還在驗證題目的小產品。生命週期六週、使用者不到一百人,時間花在 pipeline 不如花在跟使用者談;這種案子只開 Forge quick deploy。
- 伺服器在不能對外連線的內網。金融與醫療常見。Actions 打不到部署目標,得改用 self-hosted runner,成本結構不同,要在報價階段講清楚。
- 零自動測試的既有專案。先蓋 CI 只會得到一條什麼都沒檢查、卻很慢的流水線。
身為客戶,你為什麼該在意
- 時程可預測。「改完什麼時候看得到」有固定答案,不是「等我晚上有空」。
- 回滾成本近零。symlink 指回前一版通常兩分鐘恢復,不必開會決定要不要 revert,雙方都更敢試。
- 你付的是功能,不是重工。格式與靜態分析由機器擋掉,review 平均 15 分鐘,沒有一分鐘在講縮排。
如果你的團隊也想這樣做
- 先定時間預算再挑測試。講定 CI 不超過 10 分鐘,取捨自然發生;反過來做一定膨脹到沒人願意等。
- 先把部署指令寫成腳本,再談測試。就算仍由人觸發,它是檔案而不是記憶,問題就少一半。
- 把回滾練成肌肉記憶。每季在 staging 演練一次;沒演練過的回滾等於沒有這個機制。
- 快取相依套件。用 actions/cache 對
composer.lock快取,通常省三到四成 CI 時間。 - 讓失敗吵一點。部署失敗進 Slack 主頻道,不是靜悄悄的 email。
我們在哪些客戶身上跑過
某連鎖餐飲的線上訂餐系統(Laravel + Flutter)。接手前是手動 FTP,平均每次改版漏傳一兩個檔案。導入後六個月 140 次部署、2 次自動回滾、0 次因部署失誤造成的中斷。
某 B2B SaaS 後台。客戶自己有兩位工程師共同開發,同一套 workflow 直接交給他們。最明顯的改變是 PR 討論:以前一半在爭寫法,現在全部集中在商業邏輯。
常見問題
團隊只有兩三個人,值得建 CI/CD 嗎?
值得,但範圍要小。人越少越輸不起一次半夜的失誤。最小可行版本是一條跑 lint 與關鍵路徑測試的 CI,加一個一鍵部署腳本。
導入要多久、多少成本?
既有 Laravel 專案約 3 到 5 個工作天,補測試佔一半以上;持續成本是 CI 分鐘數與 Forge 訂閱,中小專案多半每月數十美元。
可以只做 CI、不做自動部署嗎?
可以,對很多團隊還是正確的第一步:先讓每個 PR 被檢查、累積信任,再打開自動部署。
migration 自動跑不危險嗎?
會,所以有兩條紀律:破壞性變更拆成兩次部署,先加後刪;部署前自動快照。Laravel migration 文件也提醒用 --force 前要清楚會執行什麼。
會把流程交接給客戶團隊嗎?
會,這是預設交付內容;workflow、部署腳本與 runbook 都在客戶自己的 repo。
下一步
如果你正在評估 Laravel 或 Flutter 專案,而過去經驗讓你對「上線」不安,我們可以先看你現在的 repo,給一份改善順序,不收費。如果你是工程師,喜歡把重複的事變成腳本、對 CI 綠燈有執念,我們長期在找這樣的人。
- Email:[email protected]
- 電話:0916-224-047
- LINE:@ufv9089p