这篇文章整理我把个人技术博客部署到低配置服务器时遇到的问题,以及最后收敛出的部署方式。这个项目本身不只是一个展示页,还包含 Next.js 前端、FastAPI 后端、MySQL、Redis、Nginx 反向代理和一键部署脚本,所以部署过程比单纯静态站点更接近真实业务系统。
背景
我的目标是让博客成为一个长期可维护的个人技术作品,同时保留后续写文章、AI 生成草稿、编辑发布和后台管理的能力。服务器资源不高,所以不能简单地把所有服务按默认配置跑起来,而是需要控制内存、启动顺序和故障恢复。
最终部署形态是:
| 模块 | 作用 |
|---|---|
| Next.js | 前端页面、博客列表、文章详情 |
| FastAPI | 文章接口、AI 草稿、发布管理 |
| MySQL | 持久化文章、草稿和基础数据 |
| Redis | 缓存、状态数据和轻量运行辅助 |
| Nginx | 对外入口、反向代理前后端 |
| Docker Compose | 统一编排和一键启动 |
遇到的问题
1. 容器能启动,但 Nginx 找不到后端
一开始 Nginx 日志提示 upstream 找不到服务。原因是生产环境里容器名称和 Nginx 配置里的 upstream 名称没有完全对齐,导致反向代理启动时解析不到后端地址。
解决思路是统一使用 Docker Compose 网络里的服务名或固定容器名,并确保 Nginx 配置与 compose 文件保持一致。这个问题提醒我,部署不是只看代码能不能跑,还要看服务之间的网络命名、启动顺序和配置一致性。
2. 后端因为环境变量格式启动失败
后端曾经因为 CORS 配置解析失败而反复重启。问题出在环境变量传入的是普通字符串,但配置层按复杂 JSON 类型解析,导致应用启动阶段直接报错。
后续处理方式是让配置解析更宽容:生产环境可以用逗号分隔的字符串,代码里再转换成列表。这样比要求服务器环境变量严格写 JSON 更适合一键部署脚本。
3. MySQL 初始化和迁移需要分清边界
MySQL 容器的 init SQL 只会在数据卷为空时执行,不能依赖它做后续线上 schema 变更。因此我把初始化和迁移分开:首次启动负责基础库表,后续变更走明确的 migration SQL。
这也是一次很实际的经验:部署脚本要考虑“第一次部署”和“已经有数据后的升级”是两种场景。
4. 低内存服务器需要主动限制资源
默认 MySQL、Redis、Node 和 Python 容器都可能占用较多内存。为了让服务稳定运行,我在生产 compose 中对各服务设置了更保守的内存限制,并减少 MySQL buffer、连接数和 Redis maxmemory。
重点不是追求极限性能,而是在资源有限的服务器上保证博客可访问、后端不反复重启、数据库能健康启动。
最终收敛
最终部署脚本做了几件事:
- 拉取或更新前后端代码。
- 生成必要的 MySQL、Redis、JWT 环境变量。
- 构建前端和后端镜像。
- 启动 MySQL、Redis、FastAPI、Next.js 和 Nginx。
- 等待数据库健康后执行迁移。
- 通过连通性检查确认后端接口和首页可访问。
收获
这次部署让我更清楚地理解了一个线上系统的基本闭环:代码只是其中一部分,真正能稳定访问还依赖配置、容器网络、数据库初始化、资源限制、日志排查和回滚思路。
对我来说,这个博客项目的价值不只是“有一个个人网站”,而是我把前端、后端、AI 内容流、数据库和服务器部署真正串起来,形成了一个可以持续维护和演示的作品。