Dokploy 跨 GitHub 帳號部署 private repo:customGit、多 Provider 與 push-to-deploy 踩坑筆記

Dokploy 的 GitHub App 裝在 A 帳號,卻要部署 B 帳號的 private repo?整理 customGit + SSH Deploy Key、多 Git Provider 並存、push-to-deploy webhook 與五個踩過的坑。

事情是這樣的:Dokploy 上原本就接了一顆 GitHub App(裝在帳號 A),跑得好好的。某天要部署一個新服務,程式卻放在另一個 GitHub 帳號 B 的 private repo 裡——而那顆 App 根本看不到 B 的 repo。

從「先繞過去讓它動」到「正確接成長期可維護」,中間踩了好幾個坑,整理成這篇。下面所有帳號、網域、ID 都用 placeholder 代換。

一句話問題

Dokploy 的 GitHub App provider 綁定在某個 GitHub 帳號上。要部署的 private repo 如果屬於另一個帳號,那顆 App 就無權限列出、也 clone 不到它。

先搞懂:Dokploy 抓 Git 有兩種來源

來源(sourceType) github(GitHub App) git(customGit)
認證方式 App OAuth token 一把 SSH 私鑰
repo / branch 下拉選單 無(自己填 URL/branch)
自動裝 webhook(push 自動部署)
需要 App 裝在該帳號 不用
適用情境 同帳號、要自動 CD 任何能 SSH 的 git server

關鍵:github 來源要那顆 App 有權限;git(customGit)只是最原始的 git clone,跟 App 完全無關。 跨帳號卡關時,customGit 是第一條活路。

解法一:customGit + SSH Deploy Key(最快,單一 repo 適用)

  1. 在 Dokploy 產一把 SSH key(或本機 ssh-keygen -t ed25519)。
  2. public key 加成「目標 repo 的 Deploy Key」,設成唯讀:
gh api -X POST repos/<ACCOUNT>/<REPO>/keys \
  -f title="dokploy-deploy" \
  -f key="ssh-ed25519 AAAA... your-public-key" \
  -F read_only=true
Code language: PHP (php)
  1. 在 compose/application 設定來源:sourceType=git[email protected]:<ACCOUNT>/<REPO>.git、branch、選剛剛那把 SSH key。
  2. 部署 → Dokploy 用私鑰 git clone → build。

Deploy Key 的好處:它只能唯讀單一 repo,權限比「能碰整個帳號所有 repo」的 GitHub App 小得多,拿來給 CI/CD clone 剛剛好。

坑①:customGit 沒有自動 webhook,autoDeploy: true ≠ push-to-deploy

這個最容易誤會。把「CD」拆成兩層就清楚了:

  • (A) 從 git 來源 build 部署:customGit 有。按一次就 clone + build。
  • (B) push 一下自動部署:customGit 沒有

autoDeploy: true 只是「收到 push 通知就部署」的開關,但要先有人通知 Dokploy——那個通知就是 repo 的 Webhook。GitHub App 會自動幫你裝這個 webhook;customGit 不會。所以 customGit 的「CD」其實是手動觸發(UI 按 Redeploy 或打 API)。要真正 push-to-deploy,要嘛自己加 webhook,要嘛改用 GitHub App。

解法二(推薦,長期/多帳號):幫帳號 B 開一顆「專屬」GitHub App

如果帳號 B 之後會部署很多東西,正解不是硬接舊 App,而是幫 B 開一顆自己的 GitHub App

  • 結果是帳號 A、帳號 B 各一顆 App、各一個 provider,兩個帳號都能部署、互不干擾。
  • 在 Dokploy 的設定裡找「Create GitHub App」,跟著流程把 App 建在帳號 B、再 Install 到 B、選 All repositories。

聽起來簡單,但這條路上有四個坑。

坑②:別想「改 installationId 共用一顆 App」

一個 provider 紀錄只綁一個 installation(一個帳號)。如果你貪方便,把既有 provider 的 installationId 從帳號 A 改成帳號 B,帳號 A 的 repo 會立刻從 Dokploy 消失——原本用 A 部署的服務之後 redeploy 全部 GG。正解是「再開一顆」,不是「改舊的」。

坑③:建立 App 時,瀏覽器一定要登入「目標帳號」

Dokploy 的 Create GitHub App 會把你導去 GitHub,用 manifest 流程建立 App,而 App 會被建在你當下瀏覽器登入的那個 GitHub 帳號。如果你想把 App 給帳號 B,瀏覽器卻登入著帳號 A,App 就建錯地方了(還得砍掉重來)。建議:先用無痕視窗登入帳號 B,再按 Create。

坑④:webhook URL 必須「公開可達」;在 Access/SSO 後面要設 bypass

兩個小坑疊在一起:

  1. 別讓 webhook 指到 LAN IP。 Dokploy 建 App 時,webhook URL 會抓「當下的 external_url」。如果那時候 Dokploy 還掛在內網 IP,App 的 webhook 就會指到外面打不到的位址,push 永遠不觸發。確認它是公開網址,例如 https://dokploy.example.com/api/deploy/github
  2. Access/SSO 閘要放行 webhook 路徑。 如果 Dokploy 前面有 Cloudflare Access(或任何 SSO 登入閘),GitHub 送來的 webhook 沒有瀏覽器 session,會被擋下。要對 /api/deploy/github 這個路徑單獨設一條 bypass(policy:everyone、decision:bypass)。

怎麼不開瀏覽器就確認 webhook URL 對不對?用 App 的 private key 簽一個 JWT 直接問 GitHub:

# pip install cryptography
import base64, json, time, urllib.request
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

b64 = lambda b: base64.urlsafe_b64encode(b).rstrip(b"=")
APP_ID = 123456                       # GitHub App 的 numeric id
key = serialization.load_pem_private_key(open("app.pem", "rb").read(), None)

now = int(time.time())
h = b64(json.dumps({"alg": "RS256", "typ": "JWT"}).encode())
p = b64(json.dumps({"iat": now - 60, "exp": now + 540, "iss": APP_ID}).encode())
sig = b64(key.sign(h + b"." + p, padding.PKCS1v15(), hashes.SHA256()))
jwt = (h + b"." + p + b"." + sig).decode()

req = urllib.request.Request(
    "https://api.github.com/app/hook/config",
    headers={"Authorization": "Bearer " + jwt,
             "Accept": "application/vnd.github+json", "User-Agent": "x"})
print(json.load(urllib.request.urlopen(req)))
# {'url': 'https://dokploy.example.com/api/deploy/github', ...}  <- 要是公開網址才對
Code language: PHP (php)

坑⑤:gh api /apps/<slug> 回 404,不代表 App 是私有的

想確認某顆 App 能不能裝到別的帳號時,別只信 gh api /apps/<slug>——它常常回 404,會讓你誤以為 App 是私有的。直接用瀏覽器或 curl 打它的公開頁面:

curl -s -o /dev/null -w "%{http_code}\n" https://github.com/apps/<slug>
# 200 = 公開(可裝到任意帳號)
# 404 = 私有(只能裝在擁有者帳號)
Code language: PHP (php)

之後怎麼選?

  • 一次性/單一 private repo/不想動帳號的 App 設定 → customGit + Deploy Key,接受手動 Redeploy。
  • 要 push-to-deploy、這個帳號之後會部署很多服務 → 幫該帳號開一顆專屬 GitHub App(一帳號一顆,多 provider 並存)。

說穿了:customGit 是「臨時通行證」,GitHub App provider 是「正式門禁」。 跨帳號卡住時先用前者讓它動,長期就升級成後者。

小抄

  • 跨帳號 private repo → 先想 customGit + Deploy Key。
  • customGit 無自動 webhook → 想要 push-to-deploy 就得加 webhook 或改用 App。
  • 多帳號 → 一個帳號開一顆 App、一個 provider;別去改舊 provider 的 installationId。
  • 建 App 時瀏覽器登入正確帳號;webhook URL 要公開;SSO 閘記得對 webhook 路徑開 bypass。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *