Public note

个人 VPS 全栈架构与 Codex 重建指南(脱敏版)

·Markdown 原文

个人 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 私网交换后端流量。

拆分原则:

  1. 低延迟、登录强相关的组件留在节点 A。
  2. 内存或 I/O 有明显峰值的构建任务移到节点 B。
  3. Gitea、CouchDB 等服务在迁移后只能有一个权威写入实例。
  4. 跨节点只传 API、同步和构建产物,不把不稳定的跨地域 NFS 当作视频热存储。
  5. 第二台 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 至少启用 SecureHttpOnly 和合适的 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. 不要照抄的反模式

  1. 低内存机器同时跑十多个常驻容器和现场 Quartz 构建。 构建任务应移到更宽裕的节点或设置严格的 systemd 资源边界。
  2. 系统盘同时放容器层、数据库、日志、媒体和备份。 大文件和备份必须有独立容量规划,磁盘超过 70% 就预警,超过 85% 应立即处理。
  3. 镜像长期使用 latest 一次普通重启就可能变成不可控升级。
  4. 为了省事暴露数据库端口。 跨节点使用 WireGuard,单机使用 Docker 网络或回环接口。
  5. 迁移数据库后两边都保持可写。 没有选主协议时,双写比短暂停机更危险。
  6. 把跨地域 NFS 当作视频热路径。 小探针成功不代表持续传输可靠;抖动会把应用、容器运行时和关机流程一起拖住。
  7. 媒体代理只调用 stream.pipe(response) 必须监听客户端中止与响应关闭,用共享 AbortSignal 贯穿权限检查和资源请求,并使用 pipeline 完成清理。
  8. 只看端口和容器状态。 登录、同步、拖动播放、Git push/pull、构建切换和恢复都要从真实客户端验证。
  9. .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. 最终原则

这套架构真正值得复制的是边界,而不是服务器名称或某份现成配置:

  1. 公网入口与内部服务分离。
  2. 身份认证与业务数据分离。
  3. Git、实时同步和网页发布分成三条独立链路。
  4. 交互业务与高峰构建任务分离。
  5. 数据权威端唯一,迁移有校验,发布有不可变 release,修改有回滚。
  6. 凭据从不进入文档、Git、聊天、截图或日志。
  7. 健康检查必须走到真实用户路径,备份必须用恢复证明。

只要这七条不被破坏,别人可以先在一台 VPS 上复现,再按真实瓶颈扩展到双节点,而不需要复制原系统的任何域名、地址或个人信息。