從 GitHub 到上線:部署 Next.js、PostgreSQL 與自訂網域

在 Zenavo 上把 Next.js 應用程式與 PostgreSQL 部署到自己的伺服器,設定內網連線、建置期與執行期環境變數、Prisma 資料庫遷移、HTTPS 網域與自動部署。

本文亦提供 English、简体中文、日本語、Español 和 Bahasa Indonesia。

把本機寫好的 Next.js 應用程式部署上線,通常要依序搞定五件事:啟動指令碼、資料庫連線、環境變數(分清建置期與執行期)、資料表遷移,以及綁定 HTTPS 網域。

本文以一個既有的 Next.js 儲存庫為例,把它和 PostgreSQL 一起跑在你自己的伺服器上。開始前,先連接一台 Linux 伺服器到 Zenavo。

1. 檢查儲存庫的 package.json

如果儲存庫沒有自訂 Dockerfile,Zenavo 會用 zbpack 自動識別 Next.js 並建置。確認儲存庫根目錄有鎖定檔(package-lock.json、pnpm-lock.yaml、yarn.lock 或 bun.lockb),且 package.json 包含標準的正式環境指令碼:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "engines": {
    "node": ">=20"
  }
}

上線前先在本機跑一次 npm run build,把 TypeScript 型別錯誤或建置期錯誤提前修掉。

2. 部署 PostgreSQL

在 Zenavo 建立專案,選擇伺服器,然後點擊 新增服務 → 資料庫 → PostgreSQL,選擇版本並部署。

資料庫跑在專案私有網路裡,預設不向公網暴露連接埠。服務就緒後,它會向同專案的其他服務共享連線變數;服務名稱叫 postgresql 時,完整連線字串變數名為 POSTGRESQL_CONNECTION_STRING。

3. 從 GitHub 新增 Next.js 應用程式

在同一個專案裡點擊 新增服務 → GitHub,授權 Zenavo GitHub App 讀取你的儲存庫,然後選擇儲存庫、分支與根目錄(如果是 monorepo,例如放在 apps/web,就把根目錄填成 apps/web)。

在環境變數裡引用剛才的資料庫:

DATABASE_URL=${POSTGRESQL_CONNECTION_STRING}

${POSTGRESQL_CONNECTION_STRING} 是 Zenavo 的跨服務變數引用,執行時會自動展開成真實的內網連線字串(如果你改了資料庫服務名稱,請以介面上實際顯示的變數名稱為準)。

預設的 next start 會自動讀取平台傳入的 PORT 環境變數。如果你自己寫了 Dockerfile 或自訂 server.js,請確保監聽 0.0.0.0 且連接埠與 Zenavo 服務裡設定的 HTTP 連接埠一致。更多設定見 Next.js 部署文件。

4. 分清 NEXT_PUBLIC_* 與伺服器端變數

Next.js 有兩類環境變數,行為差別很大:

  • 伺服器端變數(如 DATABASE_URL、金流密鑰、AI API Key):只在伺服器端程式碼中讀取,絕不要加上 NEXT_PUBLIC_ 前綴。在 Zenavo 的 變數 分頁修改後,重新啟動服務就會生效。
  • NEXT_PUBLIC_* 變數:在執行 next build 時會被直接內嵌進前端 JS 套件裡。修改 NEXT_PUBLIC_* 後必須重新建置部署,只重新啟動容器不會改變瀏覽器拿到的值。

另外,Zenavo 的建置機在建置階段連不上你伺服器內網裡的資料庫。因此:

  • 不要把 next build 寫成需要先連正式資料庫才能通過的指令碼;
  • 需要讀資料庫的頁面應使用動態轉譯(不在建置期預先轉譯靜態頁面),或在資料庫無法連線時跳過靜態預取。

5. 自動執行資料庫遷移(以 Prisma 為例)

因為建置期連不上內網資料庫,資料表遷移應該放在容器啟動前,而不是 build 指令碼裡。

使用 zbpack 建置時,可以在 package.json 裡加一個 predeploy 指令碼,它會在每次容器啟動、start 執行之前執行:

{
  "scripts": {
    "build": "prisma generate && next build",
    "predeploy": "prisma migrate deploy",
    "start": "next start"
  }
}

注意兩點:

  • 正式環境請用 prisma migrate deploy(只套用已提交到儲存庫的遷移檔),不要用開發環境的 prisma migrate dev 或 db push --force-reset。
  • 如果你使用自訂 Dockerfile,zbpack 的 predeploy 掛鉤不會自動介入,把 npx prisma migrate deploy && npm start 寫進你的容器啟動指令(CMD 或 ENTRYPOINT)即可。

6. 綁定網域與自動部署

應用程式啟動後,在服務的 網路 頁綁定你的網域(如 app.example.com),把 DNS 的 A 記錄指向伺服器 IP,等待 HTTPS 憑證自動簽發(詳見網域文件)。如果你的應用程式用了 NextAuth / Auth.js、Clerk 或 OAuth 登入,別忘了同步更新 NEXTAUTH_URL 等站點網址變數以及第三方後台的回呼網址。

開啟頁面,走一遍登入與一筆資料的新增、讀取、修改、刪除,確認讀寫都進了新的 PostgreSQL。

在服務 設定 中開啟 推送到這個分支時自動部署,之後每次 git push 都會觸發建置並滾動更新;如果是 monorepo,可以設定監聽路徑,只在該目錄有改動時才觸發建置(詳見 GitHub 部署文件)。

常見問題

建置日誌報錯 Can't reach database server at postgresql.zenavo.internal:5432? 你的 build 指令碼或某個頁面在靜態產生(SSG)階段嘗試連接內網資料庫。把資料庫遷移移到 predeploy 或啟動指令裡,並給需要查資料庫的頁面加上 export const dynamic = 'force-dynamic'。

修改了 NEXT_PUBLIC_API_URL 並重新啟動,網頁裡還是舊網址? NEXT_PUBLIC_* 是在建置時打包進前端程式碼的。點擊 重新部署(Redeploy) 重新跑一次建置即可。

部署新版本後發現有問題,怎麼回退? 在部署歷史裡可以把應用程式一鍵回滾到上一個映像檔版本。注意:服務回滾只還原應用程式映像檔與設定,不會回滾資料庫。如果新版本包含刪欄位等破壞性資料庫遷移,執行前請先在 PostgreSQL 服務的 備份與還原 裡建立一個資料庫備份。

準備好儲存庫後,在 Zenavo 控制台新增專案:先起 PostgreSQL,再連 GitHub 儲存庫、設好 DATABASE_URL 與網域,幾分鐘就能跑通從 git push 到上線的完整流程。