【Strapi 系列之八】二次开发 + 生产部署,Docker + PostgreSQL + Nginx 一把梭

前面几篇,我们已经完成了设计公司官网的内容模型、Strapi 后台和 Next.js 前端。

现在网站在本地运行得好好的,但准备上线时,问题就来了:

  • 服务器重启,网站还能自动恢复吗?

  • 更新代码,需要重新安装一遍环境吗?

  • 数据库和上传图片放在哪里?

  • 网站怎么绑定域名、配置 HTTPS?

  • 服务器出问题,能不能恢复数据?

这就是生产部署要解决的问题。

这一篇使用 Docker Compose 管理应用,以 PostgreSQL 保存业务数据,以 Nginx 作为统一入口。我们会搭建一套适合个人开发者、小型团队和中小型官网的部署架构。

需要说明:以下是可以按项目调整的部署模板,不是拿来就能适用于所有 Strapi 项目的通用镜像。实际运行前,需要根据自己的 Node.js 版本、Strapi 依赖、数据库配置和域名做相应修改。

一、整体架构:不要把所有服务堆在一起

我们使用四个容器:

服务 职责 对外开放
Nginx 域名入口、HTTPS、反向代理 80、443
Next.js 网站页面、服务端数据请求、表单接口 不直接公开
Strapi CMS 后台、REST API 不直接公开
PostgreSQL 存储内容与业务数据 不直接公开

建议使用两个域名:

  • www.example.com:访问 Next.js 官网。

  • cms.example.com:访问 Strapi API 和管理后台。

这里的 example.com 只是示例域名,上线时需要替换为自己的域名,并将 DNS 记录指向服务器。

为什么不把 PostgreSQL 的 5432 端口直接映射到公网?

因为 Next.js 和 Strapi 都可以通过 Docker 内部网络访问数据库。除非确实需要外部数据库管理工具连接,否则没有必要把数据库暴露给互联网。

二、准备服务器与项目目录

服务器需要安装:

  • Docker Engine

  • Docker Compose 插件

  • Git

  • 一个已解析到服务器 IP 的域名

项目目录可以规划成这样:

复制代码
design-site/
├── compose.yaml
├── .env
├── nginx/
│   └── conf.d/
│       └── default.conf
├── strapi/
│   ├── Dockerfile
│   ├── package.json
│   └── ...
└── web/
    ├── Dockerfile
    ├── package.json
    ├── next.config.ts
    └── ...

这里假设 strapi/ 是上一期的 Strapi 项目,web/ 是 Next.js 项目。

如果两个项目分别保存在 Git 仓库,也可以在服务器上分别拉取,再通过 Compose 指定各自的构建目录。

三、先解决最重要的问题:数据持久化

容器并不是数据库本身。容器删除、镜像升级或者重新创建,都不应该导致业务数据消失。

在 Compose 中,我们可以使用命名卷保存 PostgreSQL 数据:

复制代码
services:
  database:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${DATABASE_NAME}
      POSTGRES_USER: ${DATABASE_USERNAME}
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test:
        - CMD-SHELL
        - pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

这里使用的是命名卷 postgres_data,由 Docker 管理实际存储位置。

如果你更喜欢在项目目录里看到数据库文件,也可以使用绑定挂载:

复制代码
volumes:
  - ./postgres:/var/lib/postgresql/data

两种方式都可以持久化数据,但维护方式不同。

  • 命名卷:适合大多数生产部署,存储位置由 Docker 管理。

  • 绑定挂载:适合希望明确管理宿主机目录的场景,但需要特别注意目录权限、备份和迁移。

注意:数据库文件不应该被直接复制到 Git 仓库。生产环境的备份建议使用 pg_dump 等数据库备份工具,并将备份保存到独立存储位置。

四、为 Strapi 配置生产环境

Strapi 生产环境至少需要配置数据库连接、服务密钥和公开 URL。

在 strapi/.env 或服务器的环境变量管理系统中,准备以下变量:

复制代码
NODE_ENV=production
HOST=0.0.0.0
PORT=1337

DATABASE_CLIENT=postgres
DATABASE_HOST=database
DATABASE_PORT=5432
DATABASE_NAME=design_cms
DATABASE_USERNAME=strapi_user
DATABASE_PASSWORD=replace_with_a_strong_password
DATABASE_SSL=false

APP_KEYS=key_one,key_two,key_three,key_four
API_TOKEN_SALT=replace_with_a_random_secret
ADMIN_JWT_SECRET=replace_with_a_random_secret
TRANSFER_TOKEN_SALT=replace_with_a_random_secret
JWT_SECRET=replace_with_a_random_secret
ENCRYPTION_KEY=replace_with_a_random_secret

PUBLIC_URL=https://cms.example.com

这些只是变量名和配置结构示例。Strapi 项目中的 config/database.ts、config/server.ts 等文件必须正确读取对应变量;例如 PUBLIC_URL 是否生效,取决于你是否在项目配置中使用它。

所有密钥都必须使用安全随机值生成,不要使用上面的示例值,也不要将真实密钥提交到 Git。

另外,DATABASE_HOST=database 是关键:这里应该使用 Compose 中数据库服务的名称,而不是 localhost。在 Strapi 容器中,localhost 指向的是 Strapi 容器自身。

1. 创建 Strapi Dockerfile

在 strapi/Dockerfile 中,可以采用多阶段构建思路:

复制代码
FROM node:22-alpine AS build

WORKDIR /opt/app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:22-alpine AS runtime

WORKDIR /opt/app
ENV NODE_ENV=production

COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force

COPY --from=build /opt/app/config ./config
COPY --from=build /opt/app/src ./src
COPY --from=build /opt/app/public ./public
COPY --from=build /opt/app/dist ./dist

EXPOSE 1337

CMD ["npm", "run", "start"]

这段代码展示的是多阶段构建的基本结构。不要不经验证就直接复制到所有 Strapi 项目中。 Strapi 5 项目的运行文件、插件、上传目录和构建产物可能不同,部分依赖也可能需要在运行阶段保留。

建议先参考官方 Docker 指南,根据实际项目生成并验证 Dockerfile,再确定哪些文件需要复制到运行镜像中。

特别注意:如果使用默认本地上传存储,必须持久化上传文件目录,通常是 /opt/app/public/uploads。只持久化 PostgreSQL 并不能保护图片文件。也可以考虑使用 S3 等对象存储服务。

五、为 Next.js 构建生产镜像

Next.js 可以通过 Node.js 服务运行,也可以使用 Docker 部署。这里采用 standalone 输出模式,减少最终镜像中不必要的文件。

在 web/next.config.ts 中:

复制代码
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "standalone",
};

export default nextConfig;

然后创建 web/Dockerfile:

复制代码
FROM node:22-alpine AS build

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:22-alpine AS runtime

WORKDIR /app

ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME=0.0.0.0

COPY --from=build /app/public ./public
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static

EXPOSE 3000

CMD ["node", "server.js"]

如果项目没有 public/ 目录,构建文件时需要相应调整。

还要特别注意环境变量的构建时机:带有 NEXT_PUBLIC_ 前缀的变量会被打包进客户端代码,通常在构建时确定。因此,公开 API 地址等变量如果会影响客户端代码,需要在构建阶段正确配置。服务端密钥则应保留在运行环境中,不能放进客户端变量。

官方参考:Next.js 自托管部署。

六、用 Docker Compose 把服务连接起来

现在将数据库、Strapi 和 Next.js 组合在一起。

在项目根目录创建 compose.yaml:

复制代码
services:
  database:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${DATABASE_NAME}
      POSTGRES_USER: ${DATABASE_USERNAME}
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test:
        - CMD-SHELL
        - pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    networks:
      - backend

  strapi:
    build:
      context: ./strapi
    restart: unless-stopped
    env_file:
      - ./strapi/.env
    environment:
      DATABASE_HOST: database
      NODE_ENV: production
    volumes:
      - strapi_uploads:/opt/app/public/uploads
    expose:
      - "1337"
    depends_on:
      database:
        condition: service_healthy
    networks:
      - backend
      - frontend

  web:
    build:
      context: ./web
    restart: unless-stopped
    environment:
      NODE_ENV: production
      STRAPI_URL: http://strapi:1337
    expose:
      - "3000"
    depends_on:
      - strapi
    networks:
      - frontend

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - web
      - strapi
    networks:
      - frontend

volumes:
  postgres_data:
  strapi_uploads:

networks:
  backend:
  frontend:

这份配置把数据库放在 backend 网络中,Next.js 与 Strapi 放在 frontend 网络中,Nginx 负责对外接入。

expose 只是说明容器内部使用的端口,不会像 ports 一样映射到宿主机公网端口。

正式部署前还需要注意:

  • .env 文件必须准备好,且密码和密钥应使用强随机值。

  • strapi/.env 中应包含 Strapi 所需的密钥和数据库变量。

  • Next.js 中的 STRAPI_URL 应使用容器内部地址,不能继续写 localhost:1337。

  • Strapi 上传目录应与实际 Dockerfile 的工作目录匹配。

  • 生产环境最好使用经过验证的版本标签,定期更新并重新构建镜像,而不是无计划地升级数据库主版本。

此外,Next.js 和 Strapi 之间的启动顺序不代表应用已经完全就绪。生产环境可以进一步为应用添加健康检查,并确保反向代理在应用可用后再接入请求。

七、配置 Nginx 反向代理

在 nginx/conf.d/default.conf 中,先配置两个域名的转发规则。

复制代码
server {
    listen 80;
    server_name www.example.com;

    location / {
        proxy_pass http://web:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    server_name cms.example.com;

    client_max_body_size 20m;

    location / {
        proxy_pass http://strapi:1337;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

这份配置先完成 HTTP 反向代理。它还不是完整的生产 HTTPS 配置。

上线前必须获取可信的 TLS 证书,再为两个域名添加监听 443 的 HTTPS 配置,并将 HTTP 请求重定向到 HTTPS。证书可以通过 Certbot 等工具申请和续期,也可以由云平台的负载均衡或托管代理统一处理。

不要在证书尚未准备好的情况下直接启用引用不存在证书文件的 HTTPS 配置,否则 Nginx 可能无法启动。

Nginx 反向代理的详细说明见:NGINX Reverse Proxy。

八、启动、检查和更新

先检查 Compose 配置是否有效:

复制代码
docker compose config

构建并启动服务:

复制代码
docker compose up -d --build

查看容器状态:

复制代码
docker compose ps

查看应用日志:

复制代码
docker compose logs -f database
docker compose logs -f strapi
docker compose logs -f web
docker compose logs -f nginx

如果 Strapi 无法连接数据库,优先检查数据库健康状态、连接变量和启动日志。

如果网站打不开,检查 Nginx 配置、域名解析、防火墙端口和容器网络。

更新代码后,可以重新构建并启动:

复制代码
git pull
docker compose up -d --build

但生产环境不应该只依赖这条命令。建议在更新前备份数据库与上传文件,并先在测试环境验证构建和启动流程。数据库结构变化也需要考虑兼容性与回滚方案。

九、备份比部署更重要

网站上线后,至少需要保护两类数据:

  1. PostgreSQL 数据库:包含文章、项目案例、设置、联系记录等。

  2. Strapi 上传文件:包含本地存储的封面、Logo 和案例图片。

数据库备份可以使用 pg_dump。例如:

复制代码
docker compose exec -T database \
  pg_dump -U "$DATABASE_USERNAME" "$DATABASE_NAME" \
  > backup.sql

这条命令假设当前 Shell 已经正确设置了两个变量。也可以直接使用与数据库容器一致的账号和库名,但不要把真实密码写进脚本或提交到 Git。

备份完成后,应该检查文件是否生成、大小是否合理,并定期在隔离环境测试恢复流程。

如果使用命名卷保存上传文件,还要单独备份该卷,或改用对象存储。数据库备份并不包含这些图片文件。

生产环境建议:

  • 每天定时备份数据库。

  • 将备份复制到另一台机器或对象存储。

  • 根据业务要求设置备份保留周期。

  • 定期验证备份能否恢复。

  • 重要升级前再做一次手动备份。

不要把 docker compose down -v****当成普通的停止命令。 它会删除 Compose 管理的命名卷,可能直接删除数据库和上传文件。日常停止服务应使用 docker compose down,并在执行任何删除操作前确认数据卷和备份状态。

十、上线前的最终检查清单

  • DNS 已指向正确的服务器。

  • HTTPS 证书已安装,且续期方案可用。

  • PostgreSQL 没有暴露公网端口。

  • Strapi 和 Next.js 只通过预期的网络访问。

  • 所有密钥均已替换为安全随机值。

  • 上传目录和数据库均有持久化存储。

  • 数据库和上传文件备份均已配置。

  • Strapi 后台登录、API 读取、图片展示和表单提交均已验证。

  • 错误日志可查,服务器磁盘空间和资源使用可监控。

  • 已验证升级、重启和数据恢复流程。

总结

这一篇完成了设计公司官网的生产部署基础架构:

  • Docker Compose 负责组织服务。

  • PostgreSQL 负责持久化内容数据。

  • Strapi 负责内容管理和 REST API。

  • Next.js 负责前端展示与服务端逻辑。

  • Nginx 负责统一入口和反向代理。

真正可靠的部署不只是"网站能打开",还要保证服务可以重启、数据可以恢复、密钥不被泄露,并且升级过程可控。

下一篇:第 9 篇《Strapi vs Payload vs Directus:2026 到底选哪个?常见踩坑一次讲清》

相关推荐
瀚高PG实验室1 小时前
瀚高数据库提交数据后,是否需要COMMIT
数据库·postgresql·瀚高数据库
岁月宁静11 小时前
一个 AI 项目从本地到上线,要经过哪些步骤?全流程讲清楚
python·docker·agent
正经教主16 小时前
【FDE系列】阶段2:Day 42(UP详细版):Dockerfile 实战 — 把你的应用打包成镜像
人工智能·docker·fde
刘马想放假16 小时前
Copyparty 轻量文件共享:Docker Compose 部署、权限与多协议使用全指南
docker·容器
AI开源软件17 小时前
SpringBoot信创项目怎么验收?从数据库迁移到交付证据
postgresql·springboot·达梦·opengauss·信创·企业管理系统
岁寒良木19 小时前
麒麟 V10 (x86_64) 离线/联网部署 Docker 26.1.4 与 MaxKB v2.10 LTS 实战指南
python·docker·kylin
l1t20 小时前
DeepSeek总结的ALTER FUNCTION SET work_mem:无需全局更改即可修复 PostgreSQL 磁盘溢出
sql·缓存·postgresql
闲云自留地1 天前
K8s Ingress 全解:ingress-nginx 部署、域名路径路由、HTTPS 限流灰度一篇通
nginx·https·kubernetes
正经教主1 天前
【FDE系列】阶段2:Day 43(UP详细版):Docker Compose — 多容器一键编排
运维·docker·容器