个人 VPS 全栈架构与 Codex 重建指南(脱敏版)
[!summary] 一句话结论 这套架构的核心不是“在一台机器上堆很多容器”,而是按职责拆成七层:公网入口、身份认证、业务应用、数据存储、内容同步与发布、可选网络出口、运维保障。小规模先用 Ubuntu LTS、Docker Compose、Nginx、PostgreSQL 和 systemd;需要跨地域、隔离构建负载或独立数据后端时,再用 WireGuard 扩展为双节点。
本文来自一套实际运行的个人自托管平台,已在 2026-09-12 做过只读核对。为便于公开分享,文中不包含真实域名、公网或私网地址、主机别名、账号、仓库地址、App ID、数据库名、目录名、业务数据及任何凭据。
1. 适用场景与边界
这套方案适合一名个人或小团队在自己的 VPS 上运行:
- 多个 HTTPS 网站和 API;
- 统一登录与受保护页面;
- Headless CMS、小程序或轻量 Web 应用后端;
- 私有 Git 服务;
- Obsidian 多端同步和静态网页发布;
- 私有音视频文件及支持拖动的媒体播放;
- 可选的跨节点网络出口与流量分流;
- 自动备份、健康检查、定时任务和可回滚发布。
它不是面向大规模团队的高可用集群,也不追求 Kubernetes 式自动调度。设计目标是:少组件、边界清楚、能由 Codex 理解和重建、故障时可以逐层定位。
2. 总体架构
flowchart LR
Client["浏览器 / 手机 / Obsidian / Git 客户端"]
subgraph A["节点 A:公网边缘与应用节点"]
Edge["Nginx\nTLS / 路由 / auth_request"]
BFF["OIDC BFF\n服务端会话"]
IAM["Casdoor\n统一身份"]
API["Node.js API / 媒体代理"]
CMS["Directus"]
AppDB["PostgreSQL"]
Cache["Redis"]
Notes["Notes 内容服务 / LiveSync Bridge"]
Media["本地媒体存储"]
end
subgraph B["节点 B:数据后端与构建节点,可选"]
Gateway["Caddy\n辅助 TLS 入口"]
Git["Gitea"]
Couch["CouchDB"]
Builder["Quartz 构建器\nsystemd timer"]
Net["可选网络控制面 / 出口"]
end
Client --> Edge
Client --> Gateway
Edge --> BFF --> IAM
Edge --> API --> CMS --> AppDB
CMS --> Cache
API --> Media
Edge --> Notes
A <-->|"WireGuard 私网隧道"| B
Edge -->|"私网反代"| Git
Gateway --> Couch
Notes --> Couch
Builder -->|"拉取脱敏输入"| Notes
Builder -->|"上传已校验的不可变产物"| Edge
七层职责
| 层 | 主要组件 | 职责 | 明确不做什么 |
|---|---|---|---|
| 公网入口层 | Nginx;次节点可用 Caddy | TLS、虚拟主机、路径路由、限流、认证前置检查 | 不保存业务密码,不直接承载业务数据 |
| 身份层 | Casdoor、OIDC BFF、PostgreSQL | 登录、OIDC、服务端会话、角色和回调 | 不把 OIDC token 交给浏览器脚本 |
| 应用层 | Node.js 20、Express 5、业务 API | 聚合身份、业务规则、文件代理、短期授权 | 客户端不能直拿 CMS 管理 token |
| 内容层 | Directus 11、PostgreSQL 16、Redis 7 | 内容模型、后台管理、文件元数据、缓存 | 不直接暴露数据库或管理接口给公网 |
| 同步发布层 | CouchDB 3、Self-hosted LiveSync、Quartz 5 | Obsidian 增量同步、私有 Notes 构建、选择性公开发布 | Git push 不等于 LiveSync,LiveSync 也不等于网页发布 |
| 代码与数据层 | Gitea、对象或本地存储、离机备份 | 私有代码、附件、媒体和恢复材料 | 不能把运行中卷当作备份 |
| 运维层 | Docker Compose、systemd、日志轮转、健康检查 | 启停、资源限制、定时任务、回滚和告警 | 进程存活不等于用户路径可用 |
3. 单 VPS 与双 VPS 两种部署方式
单 VPS:最适合第一次重建
把 Nginx、身份、API、Directus、PostgreSQL、Redis、Gitea、CouchDB 和 Quartz 都放在一台机器上。只保留一个公网入口,应用端口全部绑定回环接口或只放进 Docker 内网。
建议起步规格:
- 仅网站、API、Git、轻量 CMS:4 vCPU、8 GB 内存、120 GB SSD;
- 再加 Quartz 构建、多个数据库和媒体:4 vCPU、16 GB 内存、200 GB SSD 起步;
- 大文件最好使用独立数据盘或对象存储,不要长期挤占系统盘。
单机的优点是便宜、容易理解、备份和恢复步骤少。缺点是公网入口、数据库和构建任务共享故障域;Quartz、数据库维护和媒体传输容易争抢内存与 I/O。
双 VPS:完整参考架构
节点 A 靠近主要用户,保留公网边缘、认证和交互型业务;节点 B 承载 Git、CouchDB、Quartz 构建和可选网络出口。两台机器只通过 WireGuard 私网交换后端流量。
拆分原则:
- 低延迟、登录强相关的组件留在节点 A。
- 内存或 I/O 有明显峰值的构建任务移到节点 B。
- Gitea、CouchDB 等服务在迁移后只能有一个权威写入实例。
- 跨节点只传 API、同步和构建产物,不把不稳定的跨地域 NFS 当作视频热存储。
- 第二台 VPS 不是自动高可用;没有复制、健康选主和切流机制时,它只是职责拆分与恢复节点。
4. 技术选型及理由
| 能力 | 选型 | 为什么这样选 | 什么时候换 |
|---|---|---|---|
| 操作系统 | Ubuntu 24.04 LTS | 生命周期长、资料多,Docker、WireGuard、Nginx 和 systemd 支持成熟 | 团队已有统一的 Debian/RHEL 基线时跟随团队 |
| 容器编排 | Docker Engine + docker compose 插件 |
两台以内最容易审计;卷、网络和重启策略明确;Codex 生成与排障成本低 | 至少三节点、需要自动调度和明确 HA/SLA 时再评估 Kubernetes |
| 主机进程管理 | systemd service/timer | 有依赖、超时、资源控制、日志和失败状态;比散落的 cron 更容易复建 | 已有统一容器调度平台时合并进去 |
| 主入口 | Nginx | 适合多站点、路径路由、auth_request、stream 转发和精细回滚 |
只有少量站点且主要追求自动证书时可全用 Caddy |
| 次入口 | Caddy | 配置简洁、自动 HTTPS,适合次节点的少量独立服务 | 全部流量统一回节点 A 时可以不部署 |
| 节点互联 | WireGuard | 内核级、配置小、性能稳定;避免把数据库和内部 API 暴露公网 | 云厂商已有可信私网且满足跨地域要求时可复用私网 |
| 身份 | Casdoor + OIDC + BFF | 统一登录;BFF 在服务端持有 token,浏览器只有 HttpOnly 会话 Cookie | 只有单个低风险站点时可暂用站点独立登录,但要预留迁移接口 |
| CMS | Directus 11 固定版本 | 后台、文件库、REST API 和数据模型开箱即用,适合个人内容维护 | 业务规则已远超 CMS、发布节奏受限时再自研后台 |
| 业务 API | Node.js 20 LTS + Express 5 | 小型网关和流式 I/O 开发快;生态成熟;适合媒体代理 | CPU 密集型任务拆到独立 Worker,不要硬塞进请求进程 |
| 主数据库 | PostgreSQL 16 | 事务、JSON、迁移和备份生态成熟,能覆盖身份与内容系统 | 某个上游应用只支持其他版本时按其兼容矩阵单独固定 |
| 缓存 | Redis 7 或 Valkey | 会话、缓存、队列状态轻量可靠 | 没有缓存或队列需求时不要为了“架构完整”强行部署 |
| 私有 Git | Gitea rootless | 比完整 DevOps 平台轻,适合少量私有仓库 | 多人协作、CI 和审计需求变重时再评估更完整平台 |
| Obsidian 同步 | CouchDB 3 + Self-hosted LiveSync | 多设备增量同步成熟;数据权威边界清楚 | 不需要自托管时直接使用官方同步服务,减少运维面 |
| 文档站 | Quartz 5 | 对 Obsidian Markdown、wikilink 和静态发布友好 | 只需普通博客时可用更简单的静态站生成器 |
| 媒体前端 | HTML5 Video + Plyr | 移动端控制成熟,保留原生播放能力 | 需要 DRM、转码、多码率 HLS 时接入专业视频平台或媒体流水线 |
| 网络控制面,可选 | Remnawave + Xray | 统一管理节点、出站和路由规则 | 没有这类网络需求时整层删除,不要与普通网站部署强绑定 |
版本策略
生产环境不要使用浮动的 latest 作为长期配置。每个镜像至少固定明确版本,关键服务最好固定镜像 digest;升级必须经过备份、兼容性检查、测试环境验证和可执行回滚。.env.example 只保存变量名和说明,真实值放在密码管理器、root-only 环境文件或 systemd credentials 中。
5. 三条容易混淆的数据链路
Git 链路
用于代码和 Markdown 的可审计历史:本地提交,推送到私有 Gitea,再由其他设备拉取。它适合版本恢复,但不是实时协同协议。
LiveSync 链路
Obsidian 插件把笔记增量写入 CouchDB,服务器 bridge 再生成可构建的 live vault。同步账号、端到端加密口令和数据库凭据只进入受保护配置,不进入 Git、文章或 Codex 对话。
网页发布链路
Quartz 构建器从 live vault 生成一个经过排除和扫描的临时输入树,只在摘要变化时构建。产物校验通过后打包为不可变 release,上传到入口节点,最后用原子软链接切换。构建失败时继续服务上一版。
如果要公开少量文章,不要把完整私有 vault 交给静态站生成器再依赖“过滤规则”。更安全的做法是建立一个显式公开登记区,只复制被选择的 Markdown 和白名单附件,再从这个隔离输入构建公开站点。
6. 认证与安全边界
sequenceDiagram
participant U as 浏览器
participant N as Nginx
participant B as OIDC BFF
participant I as Casdoor
participant A as 受保护应用
U->>N: 请求受保护路径
N->>B: auth_request 检查会话
B-->>N: 未登录
N-->>U: 跳转登录
U->>I: Authorization Code + PKCE
I-->>B: 回调授权码
B->>I: 服务端换取 token
B-->>U: Secure + HttpOnly 会话 Cookie
U->>N: 再次请求
N->>B: 会话检查
B-->>N: 已登录及最小用户信息
N->>A: 转发请求
必须坚持的规则:
- 公网通常只开放 SSH、HTTP/HTTPS 和确实需要的 WireGuard 端口;数据库、Redis、Directus 内部接口和 BFF 端口不直接暴露。
- 所有应用端口绑定回环接口、Docker 内网或 WireGuard 私网。
- 浏览器 JavaScript 不接触 OIDC token、CMS 管理 token、数据库密码或第三方 AppSecret。
- Cookie 至少启用
Secure、HttpOnly和合适的SameSite;不要无必要地跨子域共享。 - 每个 Compose 项目使用独立网络,只为确实需要通信的服务增加一条共享网络。
- SSH 使用密钥,关闭密码登录;管理员凭据和恢复码只保存在密码管理器。
- 日志不得记录 Authorization、Cookie、签名 URL、上传凭据和完整请求体。
- Codex 只能看到占位符和变量名;需要秘密时,从受保护位置在目标机本地注入,禁止粘贴到对话。
7. 推荐的代码与部署目录
platform/
├── apps/
│ ├── gateway-api/
│ ├── media-proxy/
│ └── notes-service/
├── stacks/
│ ├── identity/
│ ├── content/
│ ├── git/
│ ├── sync/
│ └── network-optional/
├── edge/
│ ├── nginx/
│ └── caddy/
├── systemd/
├── scripts/
│ ├── backup/
│ ├── deploy/
│ ├── health/
│ └── rollback/
├── docs/
│ ├── architecture.md
│ ├── operations.md
│ └── disaster-recovery.md
├── .env.example
└── compose.yaml
仓库只保存可复现代码、模板和文档。服务器上的运行时目录保存 .env、数据卷、release 和备份;二者不要混成一个目录,更不能把服务器 .env 反向提交进 Git。
8. 用 Codex 重建的正确顺序
阶段 0:只读盘点
先让 Codex读取操作系统、CPU/内存/磁盘、开放端口、Docker、systemd、防火墙、DNS 与已有服务。第一轮不得安装、删除、重启或修改任何东西。输出:现状表、端口冲突、风险和回滚点。
阶段 1:建立仓库和占位配置
生成目录、Compose、Nginx/Caddy 模板、systemd 单元、.env.example、备份脚本和验收脚本。运行 docker compose config、语法检查和 secret scan;仍不接管公网流量。
阶段 2:主机基线
完成系统更新、时区、SSH 密钥、防火墙、Docker、日志轮转、磁盘告警和 WireGuard。先验证节点间私网,再部署应用。
阶段 3:先数据、后应用
按 PostgreSQL/Redis/CouchDB 等数据组件,Directus/Gitea/Casdoor 等上游服务,最后 API/BFF 的顺序启动。每个阶段都必须有健康检查和备份恢复点。
阶段 4:入口与认证
先在本机或临时主机名验证 TLS、OIDC discovery、回调和 auth_request;通过后再切正式 DNS。Nginx 变更前备份,必须先 nginx -t,再 reload。
阶段 5:同步、发布和媒体
先验证 Obsidian 单设备同步,再扩展其他设备;先手工完成一次 Quartz 构建和回滚,再启用 timer。媒体服务必须验证 HTTP Range、拖动播放、客户端断开和上游连接释放。
阶段 6:端到端验收
不要把容器 healthy 当作完成。必须从真实浏览器、手机、Obsidian 和 Git 客户端分别走一遍完整用户路径。
9. 可直接交给 Codex 的主提示词
我要在自己控制的一台或两台 Ubuntu LTS VPS 上重建一套个人自托管平台。
目标架构:
1. Nginx 作为主公网入口;只有第二节点需要独立 HTTPS 时才增加 Caddy。
2. Docker Compose 管理应用和数据库;systemd 管理主机级服务与定时任务。
3. Casdoor + OIDC BFF 提供统一登录,token 只保存在服务端,浏览器只持有 Secure、HttpOnly 会话 Cookie。
4. Node.js 20 + Express 5 作为业务 API 和媒体代理。
5. Directus 11 + PostgreSQL 16 + Redis 7 作为内容后台。
6. Gitea 保存私有代码;CouchDB + Self-hosted LiveSync 同步 Obsidian;Quartz 5 构建静态站。
7. 两节点模式用 WireGuard 互联,数据库和内部 API 不暴露公网。
8. 可选网络控制面必须与网站、认证和数据层解耦。
安全规则:
- 不要让我把密码、token、私钥、Cookie、真实域名、IP 或 setup URI 粘贴到聊天。
- 仓库只创建 .env.example;真实秘密从目标机上的 root-only 文件、systemd credentials 或密码管理器注入。
- 禁止数据库和缓存监听公网;默认拒绝未列出的防火墙端口。
- 所有镜像固定版本;关键镜像固定 digest;不要使用浮动 latest。
- 每次修改前备份;每阶段给出明确回滚命令。
- 不得删除已有卷、数据库、证书或配置,除非我明确确认目标。
执行方式:
- 第一阶段只读盘点,不修改主机。
- 先给出架构差异、端口规划、目录规划、资源预算、实施步骤和风险。
- 我确认后再逐阶段执行;每阶段先本地校验,再灰度接入,最后做真实客户端验收。
- 每次报告必须区分:进程状态、容器健康、内部 API、TLS/认证、真实用户路径和数据恢复能力。
- 如果只有一台 VPS,先给单机方案;同时保留以后拆成入口节点与后端/构建节点的迁移边界。
请先开始只读盘点,并输出脱敏后的现状表。不要执行安装、重启、DNS 变更或数据迁移。
10. 验收清单
| 范围 | 必须验证的结果 |
|---|---|
| 主机 | 防火墙默认拒绝;时间同步;磁盘、inode、内存和 swap 有告警 |
| 容器 | docker compose config 通过;容器有固定版本、重启策略、健康检查和必要资源上限 |
| TLS | 证书链正确;HTTP 跳转 HTTPS;不支持的虚拟主机不会落进业务站点 |
| OIDC | 无会话会跳转;回调成功;退出后失效;浏览器存储看不到 token |
| API | 未认证返回 401;跨用户资源不可读;上游失败不泄露内部信息 |
| 数据库 | 公网无法连接;迁移可重复;备份文件可被实际恢复 |
| Git | 一台客户端 push,另一台 pull;仓库和附件备份能恢复 |
| Obsidian | 新建、修改、删除和冲突各测一次;Git、LiveSync、网页发布三条链路分别验收 |
| Quartz | 私密目录和符号链接被阻止;构建失败不替换上一版;release 可回滚 |
| 媒体 | Range 请求返回 206;手机能拖动;快速切换或关闭后上游连接归零 |
| 双节点 | WireGuard 正常;只走私网访问后端;断开隧道时故障可解释且不会出现双写 |
| 恢复 | 从空目录恢复至少一个数据库、一个 Git 仓库、一篇笔记和一个媒体文件 |
11. 备份与可观测性
最低备份集合:
- PostgreSQL:定时 custom-format dump,并保留角色和迁移版本;
- Directus:数据库、uploads 和扩展目录同时备份;
- Gitea:停止写入或使用官方一致性备份,包含仓库、数据库和配置;
- CouchDB:使用可验证的复制或一致性快照,不只复制运行中的数据库文件;
- Quartz:备份代码、插件、配置和最后两个可用 release;
- 媒体:文件本体、Directus 元数据和校验和缺一不可;
- Nginx/Caddy/systemd/WireGuard:备份模板和脱敏清单,真实密钥单独保管。
至少保留一份离开 VPS 供应商的加密备份,并定期从空环境做恢复演练。没有恢复记录的备份只能算“可能有用的文件”。
需要长期观察的指标:
- 系统盘使用率、inode 和日志增长;
- available memory、持续 swap-in/swap-out、内存和 I/O PSI;
- PostgreSQL 连接数、慢查询和备份结果;
- 容器重启次数、健康检查和 systemd failed units;
- Nginx/Caddy 的 4xx/5xx、TLS 续期和上游耗时;
- WireGuard 最近握手、丢包和持续大文件吞吐;
- 媒体代理的活跃连接、客户端中止后的上游清理;
- LiveSync 延迟、Quartz 最近一次成功 release 及当前 release 指针。
12. 不要照抄的反模式
- 低内存机器同时跑十多个常驻容器和现场 Quartz 构建。 构建任务应移到更宽裕的节点或设置严格的 systemd 资源边界。
- 系统盘同时放容器层、数据库、日志、媒体和备份。 大文件和备份必须有独立容量规划,磁盘超过 70% 就预警,超过 85% 应立即处理。
- 镜像长期使用
latest。 一次普通重启就可能变成不可控升级。 - 为了省事暴露数据库端口。 跨节点使用 WireGuard,单机使用 Docker 网络或回环接口。
- 迁移数据库后两边都保持可写。 没有选主协议时,双写比短暂停机更危险。
- 把跨地域 NFS 当作视频热路径。 小探针成功不代表持续传输可靠;抖动会把应用、容器运行时和关机流程一起拖住。
- 媒体代理只调用
stream.pipe(response)。 必须监听客户端中止与响应关闭,用共享 AbortSignal 贯穿权限检查和资源请求,并使用 pipeline 完成清理。 - 只看端口和容器状态。 登录、同步、拖动播放、Git push/pull、构建切换和恢复都要从真实客户端验证。
- 把
.env、证书、Cookie 或 LiveSync setup URI 发给 Codex。 Codex 只需要变量名、权限和注入方式,不需要看到值。
13. 关键名词
- Reverse Proxy(反向代理):统一接收公网请求,再按域名或路径转发给内部服务。
- OIDC:建立在 OAuth 2.0 之上的身份协议,用于登录和用户身份声明。
- BFF(Backend for Frontend):面向特定前端的服务端网关;在这里负责保管 OIDC token 和网站会话。
- Headless CMS:只提供内容管理后台和 API,不强制绑定前端页面。
- Docker Compose:用声明式文件管理一组容器、网络、卷和健康检查。
- WireGuard:用于节点间私网互联的轻量 VPN。
- Self-hosted LiveSync:Obsidian 的第三方同步方案,常以 CouchDB 作为远端数据层。
- Quartz:把 Obsidian 风格 Markdown 构建成静态网站的工具。
- Immutable Release(不可变发布):每次构建产生新目录,校验后切换指针,不在在线目录原地覆盖。
- Atomic Switch(原子切换):通过软链接或等价机制一次性切换新旧版本,避免用户看到半成品。
- RPO:最多能接受丢失多少时间范围的数据。
- RTO:故障后最多能接受多久恢复服务。
- PSI:Linux 对 CPU、内存和 I/O 压力等待时间的观测指标。
- HTTP Range:客户端只请求媒体文件的一段,是拖动进度和断点读取的基础。
- AbortSignal:把客户端断开传递给上游请求,避免媒体代理留下连接和内存泄漏。
14. 最终原则
这套架构真正值得复制的是边界,而不是服务器名称或某份现成配置:
- 公网入口与内部服务分离。
- 身份认证与业务数据分离。
- Git、实时同步和网页发布分成三条独立链路。
- 交互业务与高峰构建任务分离。
- 数据权威端唯一,迁移有校验,发布有不可变 release,修改有回滚。
- 凭据从不进入文档、Git、聊天、截图或日志。
- 健康检查必须走到真实用户路径,备份必须用恢复证明。
只要这七条不被破坏,别人可以先在一台 VPS 上复现,再按真实瓶颈扩展到双节点,而不需要复制原系统的任何域名、地址或个人信息。