跳到主要内容

用 Docker 部署 sub2api 并配 Caddy 反代

发布于DockerCaddy运维

记录 sub2api 和 Caddy 在 Docker 里的部署过程,sub2api 用官方一键脚本起,Caddy 用 host 网络模式反代。

前置条件

  • 一台 Linux 服务器(Docker 20.10+,支持 host-gateway
  • 一个 A 记录指向服务器的域名(如果有 AAAA 记录,也要指向这台服务器)
  • 腾讯云轻量云防火墙放行 2280443(HTTP/3 还需要放行 443/UDP

第一步:部署 sub2api

sub2api 官方提供了一键准备脚本,会下载 compose 文件、生成密码、建数据目录:

mkdir -p ~/sub2api && cd ~/sub2api
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash

这是直接执行 main 分支脚本,适合自己临时部署;要长期复现,建议固定到某个 release 或 commit。

脚本做的事:

  • 下载 docker-compose.yml(app + PostgreSQL + Redis 三件套)
  • 生成 POSTGRES_PASSWORDJWT_SECRETTOTP_ENCRYPTION_KEY 三个密码写入 .env
  • 创建 data/postgres_data/redis_data/ 数据目录

脚本只是「准备」,不会启动容器。.env 里真正硬性必填的只有 POSTGRES_PASSWORD(compose 语法层面要求),其他变量都有合理默认值。手动改两个值就够了:

nano .env
# ADMIN_EMAIL=你的邮箱@example.com
# ADMIN_PASSWORD=设一个强密码

不设也行,密码会在首次启动日志里生成。最后启动:

docker compose up -d
docker compose logs -f sub2api   # 确认没有启动或数据库连接错误

这一步结束后,sub2api 会在宿主机的 0.0.0.0:8080 监听。腾讯云轻量云防火墙是白名单模式、没有放行 8080,因此公网访问不到;但宿主机上的其他进程仍然可以访问。这里保持官方 Compose 不改,公网隔离依靠腾讯云防火墙。

第二步:部署 Caddy

Caddy 单独起一个容器,用 host 网络模式直接共享宿主机网络栈。这样 Caddyfile 里 reverse_proxy localhost:8080 就能命中 sub2api,不用记 Docker 内部网络名。

建目录,准备两个文件:

mkdir -p ~/caddy && cd ~/caddy
nano Caddyfile

Caddyfile 最简版三行就够:

sub.example.com {
    reverse_proxy localhost:8080
}

sub.example.com 换成你的域名。Caddy 默认行为已经包含:

  • 自动向 Let’s Encrypt 申请证书
  • 自动续期
  • HTTP 自动跳转 HTTPS
  • 启用 HTTP/2、HTTP/3(客户端支持且 443/UDP 已放行时)
  • 默认只启用 TLS 1.2 和 1.3(TLS 1.0/1.1 默认就禁用了,不用手动配)

想要更多优化可以扩展,但起步阶段三行就够了。

再写一个 docker-compose.yml,避免每次升级都要敲一长串 docker run

nano docker-compose.yml
services:
  caddy:
    image: caddy:2-alpine
    container_name: caddy
    network_mode: host
    restart: unless-stopped
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

启动:

docker compose up -d
docker compose logs -f caddy   # 确认没有 ACME 或端口占用错误

caddy_data 卷里存着证书和私钥,重启容器不会丢;caddy_config 卷存运行时配置缓存。 caddy:2-alpine 是滚动标签,升级前最好先备份并查看 release notes。

第三步:验证

curl -fsS https://sub.example.com/health
# 能正常返回且命令退出码为 0 就行

浏览器打开 https://sub.example.com,用配置的 admin 账号登录。

拓扑总览

部署完之后整个机器长这样:

                 公网

        ┌─────────┴─────────┐
        │  云防火墙: 80/443  │
        └─────────┬─────────┘

    ┌─────────────┴─────────────┐
    │  Caddy 容器 (host network) │
    │  反代 localhost:8080       │
    └─────────────┬─────────────┘
                  │ localhost 回环
    ┌─────────────┴─────────────┐
    │  sub2api (0.0.0.0:8080)   │
    │  ├─ PostgreSQL (内部网络)  │
    │  └─ Redis (内部网络)       │
    └───────────────────────────┘

Caddy 和 sub2api 通过 localhost 回环连起来。

常用维护命令

sub2api 那边(~/sub2api 目录):

docker compose ps                   # 看状态
docker compose logs -f sub2api      # 看日志
docker compose restart              # 重启
docker compose pull && docker compose up -d   # 升级

Caddy 那边(~/caddy 目录):

docker compose ps                                     # 看状态
docker compose logs -f caddy                          # 看日志
docker compose restart                                # 重启
docker compose pull && docker compose up -d           # 升级
docker exec caddy caddy reload --config /etc/caddy/Caddyfile   # 改完 Caddyfile 热重载,不中断

升级前先做备份;pull 会跟随镜像标签变化,不要在不了解版本变更时直接跨大版本升级。

加新服务时怎么扩展

将来再加一个服务(比如 Minecraft 服务器),步骤一样:

  1. 按同样套路建一个独立目录跑新服务,让它监听某个端口
  2. ~/caddy/Caddyfile 里追加一个域名块:
mc.example.com {
    reverse_proxy localhost:25565
}
  1. 热重载 Caddy:
docker exec caddy caddy reload --config /etc/caddy/Caddyfile

Caddy 自动为新域名申请证书,等 DNS 生效且后端正常监听后即可访问。

备份策略

这是一份停机后的冷备份。按当前脚本生成的 Compose,sub2api 数据都在 ~/sub2api,备份就是打包:

cd ~
docker compose -f sub2api/docker-compose.yml down
tar czf sub2api-backup-$(date +%F).tar.gz sub2api/
docker compose -f sub2api/docker-compose.yml up -d

.env 里的三个密码也要跟着一起备份:POSTGRES_PASSWORD 要和现有数据库匹配;换 JWT_SECRET 会让已有会话失效;丢失 TOTP_ENCRYPTION_KEY 会影响已启用的 2FA。恢复前先在临时目录演练一次,不要把“能解压”当成“能恢复”。Caddy 还要单独备份 ~/caddy/Caddyfilecaddy_data 卷,否则恢复后需要重新申请证书。

一些踩过的坑

坑一:把脚本直接在 ~/ 跑。 准备脚本会把文件铺在当前目录,一定要先 mkdir 子目录再 cd 进去执行,不然家目录被一堆 data/postgres_data/ 污染。

坑二:用 docker-compose.local.yml 当文件名执行。 准备脚本下载时会自动把这个文件重命名为 docker-compose.yml,所以直接 docker compose up -d,不用 -f 参数。

坑三:Caddy 容器加不进 sub2api 的内网。 既然 Caddy 用 host 网络模式,就不用管 Docker 内部网络名;这个方案面向 Linux,Caddyfile 反代地址保持 localhost:8080