---
title: "从 GitHub 到上线：部署 Next.js、PostgreSQL 和自定义域名"
description: "在 Zenavo 上把 Next.js 应用和 PostgreSQL 部署到自己的服务器，配置内网连接、构建期与运行时环境变量、Prisma 数据库迁移、HTTPS 域名与自动部署。"
date: 2026-11-10
language: zh-CN
url: https://zenavo.app/zh-CN/blog/deploy-nextjs-postgresql
---

# 从 GitHub 到上线：部署 Next.js、PostgreSQL 和自定义域名

把本地写好的 Next.js 应用部署上线，通常要依次搞定五件事：启动脚本、数据库连接、环境变量（分清构建期与运行期）、数据库表迁移，以及绑定 HTTPS 域名。

这篇文章以一个已有的 Next.js 仓库为例，把它和 PostgreSQL 一起跑在你自己的服务器上。开始前，先[连接一台 Linux 服务器](https://docs.zenavo.app/zh-CN/servers/add-a-server)到 Zenavo。

## 1. 检查仓库的 `package.json`

如果仓库没有自定义 `Dockerfile`，Zenavo 会用 zbpack 自动识别 Next.js 并构建。确认仓库根目录有锁文件（`package-lock.json`、`pnpm-lock.yaml`、`yarn.lock` 或 `bun.lockb`），且 `package.json` 包含标准的生产脚本：

```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`）。

在环境变量里引用刚才的数据库：

```dotenv
DATABASE_URL=${POSTGRESQL_CONNECTION_STRING}
```

`${POSTGRESQL_CONNECTION_STRING}` 是 Zenavo 的跨服务变量引用，运行时会自动展开成真实的内网连接串（如果你改了数据库服务名，以界面上实际提示的变量名为准）。

默认的 `next start` 会自动读取平台传入的 `PORT` 环境变量。如果你自己写了 `Dockerfile` 或自定义 `server.js`，请确保监听 `0.0.0.0` 且端口与 Zenavo 服务里配置的 HTTP 端口一致。更多配置见 [Next.js 部署文档](https://docs.zenavo.app/zh-CN/frameworks/nextjs)。

## 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` 执行之前运行：

```json
{
  "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 证书自动签发（详见[域名文档](https://docs.zenavo.app/zh-CN/networking/domains)）。如果你的应用用了 NextAuth / Auth.js、Clerk 或 OAuth 登录，别忘了同步更新 `NEXTAUTH_URL` 等站点地址变量以及第三方后台的回调地址。

打开页面，走一遍登录和一条数据的增删改查，确认读写都落进了新的 PostgreSQL。

在服务 **设置** 中开启 **推送到这个分支时自动部署**，之后每次 `git push` 都会触发构建并滚动更新；如果是 monorepo，可以配置监听路径，只在该目录有改动时才触发构建（详见 [GitHub 部署文档](https://docs.zenavo.app/zh-CN/deploy/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 控制台](https://zenavo.app)新建项目：先起 PostgreSQL，再连 GitHub 仓库、配好 `DATABASE_URL` 和域名，几分钟就能跑通从 `git push` 到上线的完整流程。
