Communitygithub.com

發佈到 App Platform

將網站或應用程式發佈到 DigitalOcean App Platform,並回傳其公開 URL。每當使用者要求發佈、部署、託管或上線您建置的內容時使用此技能。請用它來取代需要 Docker 且在此環境無法運作的 `publish` 技能 / `apps` 工具。涵蓋將程式碼推送到 GitHub、建立 App Platform 應用程式、等待部署,以及從建置日誌中修復失敗。

發佈到 App Platform 是什麼?

此技能自動化將應用程式發佈到 DigitalOcean App Platform 的完整流程,作為需要 Docker 的 `publish` 技能的直接替代方案。它指導使用者從建置與測試本機沙箱開始,然後建立可部署的設定,最後提出包含具體步驟(如建立 GitHub 儲存庫、設定 App Platform 應用程式、等待部署完成並回報 URL)的計劃,並等待使用者明確批准後才實際執行任何外部操作。此技能支援所有主要平台(如 Claude Code、Cursor、Codex),因為完全依賴標準 CLI 工具(git、curl、node、python)和共享憑證代理。關鍵功能包括處理靜態網站、服務(Node、Python)以及選用的託管 PostgreSQL 資料庫,還有智慧型的失敗處理,從建置日誌自動診斷常見錯誤並提出修正建議,每次修正前都需再次取得使用者同意。當部署成功時,會回報完整的公開 URL 和儲存庫 URL,並簡潔說明後續如何更新。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/jnaidu-do/qm-appplatform-skills/tree/main/app-platform-publish

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

發佈到 App Platform 是做什麼的?

流程:在沙箱中建置與測試 → 使其可部署 → 提出計劃並獲得同意 → 推送到 GitHub → 建立 App Platform 應用程式 → 等待 ACTIVE → 檢查 URL。

在使用者批准步驟 2 的計劃之前,切勿建立儲存庫、推送或建立/更新/刪除應用程式。 像「發佈它」這樣的請求並不算是批准。在沙箱中建置與測試不需要批准。

切勿使用 apps 動作 publish、docker 或 doctl — 這些在此電腦上都無法運作。所有操作都透過兩個共享憑證進行,您需透過核心的憑證代理來呼叫它們。您永遠不會看到令牌。

每個指令都使用明確的 sandbox_id,並且對於任何呼叫代理(digitalocean、github 或 github-git)的指令,需在 execute.credentials 中選擇憑證。核心只會將 AGENT_CREDENTIAL_TOKEN 提供給這些指令。請勿使用 rm -rf;應改用 mktemp -d 建立一個新的目錄。

電腦擁有的工具

Node 24 + npm、Python 3.12 + pip、git、gh(未登入;請勿使用)、curl、gcc、make。沒有 docker、doctl、jq、wget、unzip。使用 node -e 或 python3 -c 解析 JSON。

憑證(各憑證可進行的操作)

憑證主機允許的操作
digitaloceanapi.digitalocean.com在 /v2/apps 和 /v2/regions 下進行 GET/POST/PUT/DELETE
githubapi.github.com在 /user 和 /repos/jnaidu-do 下進行 GET/POST/PATCH
github-gitgithub.com (git push/clone)在 /jnaidu-do/ 下進行 GET/POST

所有內容都在共享的 GitHub 帳戶 jnaidu-do(一個部署範圍的憑證,非使用者自己的帳戶)下建立。請在計劃中說明這一點。如果使用者想要自己的帳戶,請告知他們該功能尚未設定。403 path_not_allowed / method_not_allowed 表示請求超出允許範圍 — 請不要嘗試其他變體;直接告訴使用者這是不允許的。

Broker 輔助函式

每個指令貼上此函式一次(shell 狀態不會保留)。它會將 HTTP 狀態碼輸出到 stderr,並將回應主體輸出到 stdout:

bk() { # bk <credential> <METHOD> <url> [json-body]
  p=$(node -e 'const [c,m,u,b]=process.argv.slice(1);console.log(JSON.stringify({credential:c,method:m,url:u,headers:{accept:"application/json","content-type":"application/json"},...(b?{body:b}:{})}))' "$1" "$2" "$3" "${4:-}")
  curl -fsS -X POST "$AGENT_API_URL/v1/credentials/broker" -H "x-agent-capability: $AGENT_CREDENTIAL_TOKEN" \
    -H "content-type: application/json" -d "$p" |
  node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const r=JSON.parse(s);console.error("HTTP "+r.status);console.log(r.body)})'
}

1. 使其可部署(推送前先檢查)

應用程式會以一般的容器形式在 App Platform 後方執行,而非在此沙箱中。

  • 監聽 $PORT,綁定到 0.0.0.0(App Platform 會設定 PORT;預設為 8080)。切勿硬編碼其他連接埠。
  • 純 HTML/CSS/JS 或可建置成靜態檔案的框架(Vite、CRA、Astro):以靜態網站形式部署(build_command、output_dir)。建議以此方式優先於為靜態頁面執行伺服器。
  • 任何需要伺服器的內容:使用服務形式,並設定 build_command、run_command、http_port。標準配置無需額外設定即可運作:Node 需要包含 start 腳本的 package.json;Python 需要 requirements.txt 和一個啟動指令(例如 gunicorn app:app --bind 0.0.0.0:$PORT)。儲存庫中的 Dockerfile 也可運作(需設定 dockerfile_path),但您無法在此處測試建置,所以請保持簡單。
  • 無持久性磁碟。 任何寫入檔案系統的內容都會在每次部署時遺失。請勿使用 SQLite 檔案、上傳到磁碟或使用 JSON 檔案來儲存狀態。如果應用程式需要資料庫,請新增一個受管理的 Postgres(請參閱下方);否則請保持無狀態。
  • 先在本地執行:npm install,使用 PORT=8080 啟動,並用 curl localhost:8080 測試。只發佈能運作的內容。
  • 新增包含 node_modules 等的 .gitignore。請勿提交機密。切勿輸出令牌。

如果應用程式使用 Postgres

App Platform 的 ${db.DATABASE_URL} 包含 sslmode=require,而 Node 的 pg 會因為受管理的 CA 而拒絕連線(SELF_SIGNED_CERT_IN_CHAIN)。從 URL 中移除 sslmode 並設定 ssl: { rejectUnauthorized: false }:

function normalizeDatabaseUrl(raw) {
  const u = new URL(raw);
  u.searchParams.delete("sslmode"); u.searchParams.delete("ssl");
  return u.toString();
}
const pool = new Pool({ connectionString: normalizeDatabaseUrl(process.env.DATABASE_URL), ssl: { rejectUnauthorized: false } });

在規格中加入 "databases": [{"name":"db","engine":"PG","production":false,"version":"16"}],並在需要資料庫的每個元件中加入 {"key":"DATABASE_URL","scope":"RUN_TIME","value":"${db.DATABASE_URL}"}。初次連線時請重試約 30 秒:資料庫可能仍在啟動中。只有在應用程式確實需要資料庫時才新增(會產生額外費用)。

2. 提出計劃並詢問(在此停止,直到對方同意)

一旦應用程式建置完成並可在本地執行,請停止並向使用者發送一個簡短的計劃,然後等待回覆。 在他們批准之前,請勿執行任何 bk 呼叫、git push 或建立儲存庫/應用程式。計劃應包含:

  1. 您建置了什麼以及它已在本地執行(一行)。
  2. 您將採取的步驟,按順序,每個步驟說明會觸及哪些部分:
    • 建立 GitHub 儲存庫 jnaidu-do/<name> — 公開,位於共享的 jnaidu-do 帳戶下(非使用者自己的帳戶)
    • 使用 git 將應用程式的檔案推送到該儲存庫(列出包含的內容;確認無機密)
    • 在 nyc 區域建立 App Platform 應用程式 <name>:一個靜態網站(少量無需費用)或一個服務(basic-xxs,約每月 $5)— 說明是哪一種以及費用
    • 等待建置(3–5 分鐘),然後提供公開 URL
  3. 哪些內容會變成公開: 儲存庫和即時網站對網際網路上的任何人都可讀。
  4. 未經再次詢問您不會做的事: 推送後續變更、編輯規格或刪除任何內容。

最後提出一個直接的問題,例如:「我應該按照上述方式建立儲存庫和應用程式嗎?您也可以變更名稱、將儲存庫設為私人(需要授予 DO GitHub App 權限),或說不要。」如果有相關替代方案(靜態 vs 服務、不同名稱),請一併提供。如果使用者拒絕,請提供檔案或準備好程式碼的儲存庫並停止。

在進行以下操作前,請另外再詢問一次:任何後續的 git push(更新)、變更應用程式規格,以及刪除應用程式。

3. 推送到 GitHub

使用您在步驟 2 中提議的名稱(qm-<short-kebab-name>)(小寫)。如果名稱已被使用(422),請新增一個簡短的後綴。

# 建立儲存庫(公開:App Platform 之後無需額外認證即可複製)
bk github POST https://api.github.com/user/repos '{"name":"qm-hello-site","private":false}'

# 從一個包含應用程式的新乾淨目錄推送(不含 node_modules)
cd "$(mktemp -d)" && cp -r /path/to/app/. . && git init -q -b main && git add . &&
git -c user.name=QM -c [email protected] commit -qm "Initial commit" &&
git remote add origin "$AGENT_API_URL/v1/credentials/git/github-git/jnaidu-do/qm-hello-site.git" &&
git -c http.extraHeader="x-agent-capability: $AGENT_CREDENTIAL_TOKEN" push origin HEAD:refs/heads/main

後續更新:以相同方式提交並推送(HEAD:refs/heads/main),但只有在告知使用者變更內容並獲得同意之後 — 每次推送都需要新的批准,原始計劃不涵蓋後續推送。

4. 建立 App Platform 應用程式

bk digitalocean POST https://api.digitalocean.com/v2/apps '{"spec": {
  "name": "qm-hello-site",
  "region": "nyc",
  "services": [{
    "name": "web",
    "github": {"repo": "jnaidu-do/qm-hello-site", "branch": "main", "deploy_on_push": true},
    "build_command": "npm install",
    "run_command": "npm start",
    "http_port": 8080,
    "instance_size_slug": "basic-xxs",
    "instance_count": 1
  }]
}}'

靜態網站替代 services:使用 "static_sites": [{"name":"web","github":{...},"build_command":"npm run build","output_dir":"dist"}](純 HTML:省略 build_command,output_dir 為 "/";對於單頁應用程式,請新增 "catchall_document":"index.html")。

回應中包含應用程式的 id。請保留它。應用程式名稱由小寫字母、數字、連字號組成,長度 2–32 個字元。

如果建立失敗並出現 GitHub 儲存庫存取錯誤(「無法存取該儲存庫」):表示 DigitalOcean GitHub App 未授予此儲存庫的存取權限。請告訴使用者開啟 https://github.com/apps/digitalocean/installations/select_target 並授予對 jnaidu-do 儲存庫的存取權限,然後告知完成;重新嘗試同一個請求。如果使用者無法做到,請回退到通用的公開複製來源("git": {"repo_clone_url": "https://github.com/jnaidu-do/qm-hello-site.git", "branch": "main"}),並告知使用者後續更新需要重新部署(請參閱下方)。

5. 等待 ACTIVE

實際建置需要 3–5 分鐘。每 30 秒輪詢一次,並告知使用者您正在等待:

bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID/deployments?per_page=1" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const d=JSON.parse(s).deployments[0];console.log(d.id,d.phase,d.progress?.success_steps+"/"+d.progress?.total_steps)})'

階段:PENDING_BUILD → BUILDING → DEPLOYING → ACTIVE。ERROR 或 CANCELED 表示失敗;請前往「失敗時」。大約 12 分鐘後停止輪詢,並告知使用者目前的狀態。

6. 回報 URL

bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).app.live_url))'

嘗試 curl -s -o /dev/null -w '%{http_code}' <live_url> — 預期得到 200。此電腦通常無法連線到新的 *.ondigitalocean.app 網站(連線失敗)。如果 curl 失敗但部署狀態為 ACTIVE,這不是部署失敗:請告知您無法從此處驗證,並請使用者開啟該 URL。提供使用者完整的 https://…ondigitalocean.app URL 和儲存庫 URL。該應用程式在網際網路上是公開的。

失敗時

請自行閱讀日誌;不要要求使用者提供。先取得日誌 URL,然後擷取它:

bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID/deployments/$DEPLOYMENT_ID/logs?type=BUILD"
# 主體中包含 "live_url"(或 "historic_urls");使用 curl 取得該 URL(無需認證)

type 可以是 BUILD、DEPLOY 或 RUN(靜態網站只有 BUILD)。比對信號:

信號修正方式
Missing script: start / 找不到指令錯誤的 build_command / run_command,或缺少 start 腳本
容器退出,健康檢查失敗未監聽 $PORT / 未綁定到 0.0.0.0,或啟動時崩潰
SELF_SIGNED_CERT_IN_CHAIN缺少 Postgres SSL 輔助函式(請參閱上方)
Cannot find modulepackage.json 中缺少相依套件,或提交的 node_modules 不一致
Vite "Blocked request. This host is not allowed"設定 preview: { allowedHosts: true }(或將建置好的 dist 作為靜態網站提供服務)
建置「已跳過」/ 數秒內變為 ACTIVE原始碼未變更;推送一個真正的提交

告訴使用者根本原因(確切的日誌行)以及您建議的修正方式,並在推送修正或變更規格前先詢問 — 每次推送和每次規格變更都需要各自的同意。在使用者批准後,修正程式碼、提交並推送(deploy_on_push 會自動重新部署),然後再次輪詢。若要變更規格,請使用 PUT /v2/apps/$APP_ID 並附上 {"spec": {...}}(先用 GET /v2/apps/$APP_ID 取得目前規格再編輯)。對於一般的 git 來源,使用 POST /v2/apps/$APP_ID/deployments 並附上主體 {"force_build":true} 來強制重建。請勿透過刪除並重建應用程式來修復失敗 — 這會變更 URL。請向前修正。三次不同的失敗嘗試後放棄,並回報您嘗試過的內容。讀取日誌不需要批准。

刪除

僅在使用者明確要求時,並先說出應用程式名稱。DELETE /v2/apps/$APP_ID 會移除應用程式。GitHub 儲存庫會保留下來(此憑證無法刪除儲存庫);請告知使用者。

報告

結束時提供:即時 URL、儲存庫 URL,以及一行說明如何更新(「請告訴我要變更 X,我會推送它」)。

相關技能