Skip to content

10 — 部署规范 ​

立规时间:2026-11-08 核心原则(用户明确要求):软件一律用 Docker 运行,除非有"必须裸装"的理由。


一、核心原则 ​

默认:容器化 ​

所有业务软件、中间件、数据库、工具链,默认以容器方式运行:

  • 通过 docker compose 声明式管理
  • 用官方镜像,不自行编译
  • 状态数据用 named volume 或 bind mount 持久化

容器化的收益(为什么这么定) ​

收益说明
环境隔离各软件依赖互不污染,系统只用 Ubuntu 基础包
版本可控换版本 = 改 tag + 重建,不担心残留
可回滚镜像 tag 固定即版本固定,出问题秒回上一版
清理彻底docker compose down -v 即可完全清除
可迁移compose 文件 + volume 备份即可搬到新机
宿主机干净不污染 apt、不用担心 apt autoremove 误删依赖

裸装例外(必须满足以下之一) ​

例外条件示例
必须在网络栈就绪前运行ufw(防火墙)、cloud-init
需要直接操作宿主机内核/网络容器运行时本体 docker-ce、containerd
需要在容器外启用才有效fail2ban(封禁宿主 iptables)
作为容器运行时的前置依赖shadowsocks-libev / privoxy(为 dockerd 拉镜像提供出口)
官方明确不支持容器化—

二、当前裸装清单(及理由) ​

软件裸装理由判断
docker-ce / containerd容器运行时本体,无法自举✅ 合理
ufw防火墙需内核层生效✅ 合理
fail2ban需写宿主 iptables 封禁✅ 合理
shadowsocks-libev + privoxy为 docker pull 提供出口;装进容器会形成先有鸡还是先有蛋✅ 合理
cloud-init / systemd / 内核等系统基础组件✅ 合理
python3 / git / curl系统自带或运维工具链✅ 可接受

除以上之外,不应再有裸装软件。

三、容器化清单 ​

服务镜像目录端口状态
Caddy 2.11.9caddy:2.11.9-alpine/opt/stacks/caddy/127.0.0.1:80✅ 运行中(Web 入口)
Docs sitetenodx/docs:1.0.0(自建,VitePress→Caddy)/opt/stacks/docs/127.0.0.1:8088✅ 运行中(见 14-文档站.md)
Firmware Registrytenodx/fw-registry:1.0.0(自建,FastAPI)/opt/stacks/fw-registry/127.0.0.1:8000✅ 运行中(见 13-资源服务器.md)
PostgreSQL 18postgres:18-alpine/opt/stacks/postgres/127.0.0.1:5432✅ 运行中
Uptime Kuma 2.5.9louislam/uptime-kuma:2/opt/stacks/uptime-kuma/127.0.0.1:3001✅ 运行中
NapCat(OneBot 11)mlikiowa/napcat-docker:latest/opt/stacks/napcat/127.0.0.1:6099/3000/3002✅ 运行中(待 QQ 登录)

NapCat 是唯一需要 privileged: true 的容器 —— 镜像的容器环境伪装逻辑要求 CAP_SYS_ADMIN。这是已知高权限风险,评估见 11-应用服务.md 第 2 节。

Caddy 是 Web 入口,配置见 12-Web入口.md。新增 Web 服务应挂到 Caddy 后面, 而不是再开一个公网端口。

自建镜像的源码位置:server/fw-registry/、docs-site/。 重建方式均为 docker compose up -d --build(构建上下文就是该目录)。

公网暴露面(原则) ​

当前只有 22/tcp 对公网开放。 新增服务一律 127.0.0.1 绑定, 需要 Web 访问就挂到 Caddy(12-Web入口.md 第 6 节)。 任何时候都不应出现第二个 0.0.0.0 监听端口,除非明确决定并记录理由。


四、部署约定(新增服务时照此执行) ​

1. 目录结构 ​

/opt/stacks/<service-name>/
├── compose.yaml      # 必须(不用 docker run)
├── .env              # 密码等敏感变量,权限 600
└── backup.sh         # 有状态服务必须提供备份脚本

2. compose.yaml 必备项 ​

yaml
services:
  xxx:
    image: <官方镜像>:<明确版本>   # 不用 latest,锁版本便于回滚
    container_name: tenodx-xxx
    restart: unless-stopped       # 开机自启 + 崩溃重启
    environment:                  # 敏感值走 .env,不写进 yaml
      - FOO=${FOO}
    ports:
      - "127.0.0.1:<port>:<port>" # 内部服务只绑 loopback!
    volumes:
      - data:/path/in/container   # 状态数据必须持久化
    healthcheck:                  # 必须有,否则无法判断就绪
      test: [...]
    logging:                      # 必须有,否则日志撑爆磁盘
      driver: json-file
      options: { max-size: "50m", max-file: "3" }
    networks:
      - tenodx-net

volumes:
  data:
    name: tenodx-<service>-data

networks:
  tenodx-net:
    name: tenodx-net
    external: false                 # 首次由本栈创建,之后复用

3. 端口暴露规则(重要) ​

⚠️ Docker 发布端口会绕过 ufw。-p 8080:80 等于向公网开放 8080,即使 ufw 没放行。

服务类型端口写法
数据库、缓存、内部 API127.0.0.1:PORT:PORT —— 只允许宿主机访问(SSH 隧道运维)
需要被其他容器访问不发布端口,通过 tenodx-net 网络用服务名连接
需要对公网提供 Web由 nginx 容器统一接 80/443,其余全部 loopback

4. 网络约定 ​

所有服务加入同一个 tenodx-net 桥接网络,容器间用服务名互相访问,不走宿主机端口。

5. 版本策略 ​

  • 使用明确版本 tag(postgres:18-alpine),不用 :latest
  • 升级流程:改 tag → docker compose up -d → 验证 → 失败则改回 tag 重建

6. 日志 ​

  • 依赖 daemon.json 的全局日志轮转(50 MB × 3)
  • 每个 compose 服务再显式声明一次,双保险

7. 备份 ​

有状态服务必须提供 backup.sh,并挂 /etc/cron.d/ 定时执行:

  • 逻辑备份优先(数据库用 pg_dump / mysqldump),不要直接文件拷贝数据目录
  • 保留 7 天,备份文件权限 600
  • 必须验证备份完整性(gzip -t + 非空)

8. 监控(强制) ​

用户要求:"uptime 应该及时追加新服务的检测,所有服务都应该被监测,加入定期维护。"

任何新增服务都必须在同一次变更里加上 Uptime Kuma 监控,否则视为变更未完成。

bash
# 1) 在 /opt/stacks/uptime-kuma/monitors.json 追加一条
{ "name": "服务名", "type": "http", "url": "http://tenodx-xxx:8080/health", "interval": 60 }

# 2) 同步(幂等)
/opt/stacks/uptime-kuma/sync.sh

按服务位置选择监控类型:

服务位置类型地址写法
同 tenodx-net 的容器http / postgres / 原生用容器名:tenodx-xxx:端口
监听 0.0.0.0 的宿主服务port / httphost.docker.internal:端口
只绑 127.0.0.1 的宿主服务push加到 host-checks.sh,由宿主 cron 推

四个必知陷阱(详见 11-应用服务.md 第 5 节):

  1. TCP 检查的类型名是 port,写 tcp 会报 Unknown Monitor Type
  2. 容器里的 127.0.0.1 指容器自己,不是宿主机
  3. docker-proxy 会先接受 TCP 握手,对发布端口用 port 类型会假阳性 UP
  4. push 的 msg 不能含空格(未编码 URL 会让推送静默失败)

五、常用运维命令 ​

bash
cd /opt/stacks/<service>

docker compose ps                 # 状态
docker compose logs -f --tail 50  # 日志
docker compose restart            # 重启
docker compose up -d              # 应用变更
docker compose down               # 停止并删除容器(保留卷)
docker compose down -v            # ⚠️ 连数据卷一起删除
docker compose config             # 校验 yaml

备份与恢复(PostgreSQL 示例) ​

bash
# 手动备份
/opt/stacks/postgres/backup.sh

# 恢复
gunzip -c /var/backups/postgres/tenodx-YYYYMMDD-HHMMSS.sql.gz \
  | docker exec -i tenodx-postgres psql -U tenodx -d tenodx

进入数据库 ​

bash
# 容器内(最简单)
docker exec -it tenodx-postgres psql -U tenodx -d tenodx

# 或从本机经 SSH 隧道(端口只绑了 loopback,必须走隧道)
ssh -L 5432:127.0.0.1:5432 tenodx
# 然后本机: psql -h 127.0.0.1 -U tenodx -d tenodx

六、待办 / 后续规划 ​

项说明
docker compose 版 nginx部署 Web 时用容器 nginx,接 80/443
应用容器待技术栈确认(Node / Java / Python)
日志集中可选:接入 loki 或 journald driver
监控可选:node-exporter + cadvisor
PostgreSQL✅ 已按本规范容器化部署

TenoDX · 内部文档