---
title: "个人 VPS 全栈架构与 Codex 重建指南（脱敏版）"
canonical: "https://gomars.fun/pages/9/"
updated: "2026-09-12T07:41:08Z"
---

# 个人 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. 总体架构

```mermaid
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. 认证与安全边界

```mermaid
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. 推荐的代码与部署目录

```text
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 的主提示词

```text
我要在自己控制的一台或两台 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 上复现，再按真实瓶颈扩展到双节点，而不需要复制原系统的任何域名、地址或个人信息。
