从 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 到上线的完整流程。