← 返回博客

Docker Compose 一键部署科学上网网关:自动更新、健康检查与故障自愈实战(2026版)

📌 一分钟总结

自建科学上网网关最难的从来不是第一次搭起来,而是长期不出问题。本文用 Docker Compose 编排网关,配合 Watchtower 自动更新镜像、healthcheck + autoheal 健康检查与故障自愈、日志与磁盘保护,把「三天两头登录 VPS」变成「几个月不用管」,并给出自建与机场的真实成本对比。

自建科学上网网关,最难的不是第一次搭起来,而是搭起来之后怎么长期不出问题。协议要更新、镜像要升级、容器可能悄悄挂掉、磁盘会被日志塞满……2026 年,凡是认真玩自建的人,几乎都绕不开一个答案:Docker Compose。把网关服务声明式地编排起来,配合自动更新和健康检查,就能把「三天两头要登录 VPS 看一眼」变成「几个月不用管」。

这篇文章是运维自动化专题,重点讲三件事:Compose 怎么编排网关、Watchtower 怎么自动更新镜像、healthcheck + autoheal 怎么让容器挂了自动恢复。如果你还没搭过多协议网关,建议先看这篇 Docker 部署科学上网服务全攻略,把 Shadowsocks / Trojan / Hysteria2 的多协议底座搭好,再回来做自动化;本文默认你已经有一个能跑的容器化网关。

一、为什么自建网关要上 Docker Compose

很多人的自建网关是从一行 docker run 开始的:

docker run -d --name xray \
  -v /etc/xray:/etc/xray \
  -p 443:443 \
  --restart unless-stopped \
  ghcr.io/xtls/xray-core:latest

单容器阶段这样没问题。但网关一旦多起来——核心代理一个容器、伪装站点一个容器、监控一个容器、日志清理一个容器——docker run 的劣势就暴露了:参数散落在 shell 历史里,启动顺序靠手动,改一个端口要先把整条命令找出来重敲一遍。而且裸容器之间没有依赖关系,你无法声明「先起 nginx 再起 xray」这种顺序。

Docker Compose 解决的就是这件事。一个 docker-compose.yml 文件把整个网关的所有服务、网络、卷、资源限制、健康检查全部声明出来:

  • 声明式:整个网关的配置在一个文件里,git 可版本化,换机器复制过去就能起
  • 依赖编排:depends_on 声明服务先后,condition 还能等健康检查通过再启动下游
  • 一键管理:docker compose up -d 启动、docker compose ps 看状态、docker compose logs 看日志、docker compose down 整体下线
  • 可回滚:配置变更前备份 compose 文件,出问题一条命令恢复

一句话:docker run 适合「试一下」,Compose 适合「长期跑」。自建网关是长期跑的东西,从第一天就用 Compose,后面省的事远超那几分钟的迁移成本。

二、网关目录设计与 Compose 骨架

推荐把整个网关放在一个独立目录里,方便备份和迁移:

/opt/proxy-gateway/
├── docker-compose.yml
├── .env                 # 端口、域名、凭据等环境变量
├── config/
│   ├── xray/
│   │   ├── config.json
│   │   └── cert/        # TLS 证书
│   └── nginx/
│       └── conf.d/
└── data/                # 持久化数据(如有)

核心的 docker-compose.yml 骨架如下(以 Xray 为例,sing-box 的写法完全类似):

services:
  xray:
    image: ghcr.io/xtls/xray-core:latest
    container_name: xray
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./config/xray:/etc/xray
    environment:
      - TZ=Asia/Shanghai
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    healthcheck:
      test: ["CMD", "xray", "api", "statsquery", "--server=127.0.0.1:10085"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

几个关键点的选择逻辑:

  • network_mode: host:代理服务走 host 网络最简单——端口直接暴露、UDP 性能好、不用折腾端口映射。缺点是没有容器网络隔离,但网关类服务本身就要监听公网端口,host 是主流选择。怕隔离问题的可以用 bridge + 显式 ports。
  • restart: unless-stopped:容器崩了、VPS 重启了都会自动拉起。注意区分:always 在 Docker daemon 启动时无条件重启,unless-stopped 会尊重「手动 stop 过」的状态,运维时更不容易被系统拉起来捣乱。
  • logging 限制:json-file 驱动默认无限增长,不限制的话日志能撑爆磁盘。max-size: 10m + max-file: 3 把单个容器日志锁死在 30MB 以内——这是自建 VPS 最常见的隐形杀手,见第六节。
  • 镜像 tag 用 latest 还是固定版本:见第四节,这是自动更新的关键决策。

VPS 选型这里多说一句:Compose 编排本身对配置要求很低,1 核 512MB 的入门机器就能跑;但线路质量决定实际体验,买机器前建议先看这篇 2026 年 VPS 选购指南,搬瓦工、Vultr、RackNerd 的 CN2 GIA / 9929 线路差异讲得很细。

还有一个选型前就该想清楚的问题:自建网关的出口 IP 是机房 IP,流媒体解锁、跨境电商多账号这类对 IP 纯净度敏感的场景会天然受限。如果你主要就是干这个的,仙路湾 这类主打住宅 IP / 原生 IP 的机场比自建更省心——开箱即用,不用自己折腾 IP 质量。纯自建党可以跳过这段,继续往下看编排。

三、Compose 依赖编排:让整套服务按顺序健康启动

多容器网关(比如 Trojan + Nginx 反代 + acme.sh 证书)最烦人的问题是启动顺序。容器是并行启动的,如果 Nginx 先起来了、证书还没续好,反代就会报错。Compose 的 depends_on 可以控制:

services:
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./config/nginx:/etc/nginx/conf.d
      - ./config/xray/cert:/etc/nginx/certs:ro
    depends_on:
      acme-sh:
        condition: service_completed_successfully

  acme-sh:
    image: neilpang/acme.sh:latest
    command: daemon
    volumes:
      - ./config/xray/cert:/acme.sh
    environment:
      - CF_Token=${CF_Token}
      - CF_Zone_ID=${CF_Zone_ID}
    restart: unless-stopped

condition: service_completed_successfully 表示 acme-sh 容器成功执行完(证书续期任务)之后 Nginx 才启动。配合 restart: unless-stopped,证书每 60 天自动检查续期,Nginx 始终加载最新证书,全程不用登录服务器。这套「证书自动续期 + 反代自动就绪」的思路,在 CDN 加速与 Cloudflare 实战指南 里有更完整的展开。

如果你的网关涉及多种协议共存(Shadowsocks + Trojan + Hysteria2),协议选择和落地的细节参考 主流代理协议横向对比 和前面的 Docker 部署全攻略,Compose 层只需要把每个协议各声明一个 service。

四、Watchtower 自动更新:镜像升级不再手动

自建网关最容易被忽视的维护项是「镜像升级」。Xray、sing-box 更新频率不低,安全修复和性能优化都在新版本里,但手动更新的流程是:ssh 登录 → docker compose pull → docker compose up -d,偶尔还要处理配置兼容问题。时间一长,多数人的镜像就停在几个月前了。

Watchtower 就是干这个的:它定期检查容器镜像是否有新版本,有就自动拉取并重建容器。在 Compose 里加一个服务:

  watchtower:
    image: containrrr/watchtower:latest
    container_name: watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - TZ=Asia/Shanghai
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_SCHEDULE=0 0 4 * * *
      - WATCHTOWER_LABEL_ENABLE=true

几个配置要点:

  • WATCHTOWER_SCHEDULE:cron 表达式,上面是每天凌晨 4 点检查一次。避开晚高峰,省得更新瞬间影响正在用的人。
  • WATCHTOWER_LABEL_ENABLE=true + 容器加 com.centurylinklabs.watchtower.enable=true 标签:只更新你明确标记的容器。强烈建议开这个——否则 Watchtower 会把机器上所有容器(包括它自己、包括你随手起的其他服务)都更新一遍,不可控。
  • WATCHTOWER_CLEANUP=true:更新后自动删除旧镜像,防止磁盘被历史镜像占满。
  • 只对网关容器开启:在 xray、nginx 等服务的定义里加一行 labels: - "com.centurylinklabs.watchtower.enable=true",其他容器不标,Watchtower 就不会碰。

给容器加标签的完整写法:

services:
  xray:
    image: ghcr.io/xtls/xray-core:latest
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
    # ... 其余配置同前

自动更新的两个大坑,提前说清楚:

第一个坑是镜像 tag 用 latest。latest 是移动靶,新版本可能改配置格式(比如 Xray 大版本升级时 config.json 结构变化),Watchtower 拉下来重建后容器起不来,网关就断了。稳妥做法有两种:一是镜像 tag 固定到具体版本(如 xray-core:v25.x.x),Watchtower 只会在你手动改 tag 时才更新——但这等于放弃自动更新;二是保持 latest,接受「偶尔更新失败」的小概率,靠第四节的健康检查 + autoheal 自动兜底,更新失败容器会处于 unhealthy 状态被重启或回滚。对网关这种「挂了能快速恢复」的服务,我倾向后者——毕竟安全补丁跟不上的风险,比偶发更新失败大得多。

第二个坑是「更新即重启」的时机。默认 Watchtower 发现新版本立刻重建容器,正在跑的关键连接会瞬间断开。用 WATCHTOWER_SCHEDULE 把检查时间放到凌晨低峰期,影响最小。如果追求更平滑,可以加 --rolling-restart(逐个容器滚动重启),但网关类服务一般只有一个核心容器,直接重建问题不大。

五、健康检查 + autoheal:容器挂了自动恢复

restart: unless-stopped 只能处理「容器进程崩溃退出」的情况。更常见也更隐蔽的问题是:容器进程还活着,但服务已经坏了——比如 xray 进程僵死、端口被占用、配置热加载失败。这时候 restart 策略不会触发,你的网关「看起来在跑,实际上连不通」。

healthcheck 就是给容器装一个「体检器」,定期探测服务是否真的健康:

    healthcheck:
      test: ["CMD", "xray", "api", "statsquery", "--server=127.0.0.1:10085"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

参数含义:每 30 秒执行一次探测命令,5 秒超时,连续 3 次失败标记为 unhealthy,启动后给 10 秒宽限期(避免刚启动还没就绪就被误判)。探测命令按服务写:Xray 用 api statsquery,Nginx 用 wget 或 curl 探测本地端口,sing-box 可以用 sing-box check 校验配置后配合端口探测。

光有 healthcheck 还不够——它只是「报告」状态,不会「修复」。autoheal 容器负责第二步:监听 Docker 事件,发现 unhealthy 容器就自动重启它。加一个服务:

  autoheal:
    image: willfarrell/autoheal:latest
    container_name: autoheal
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - AUTOHEAL_CONTAINER_LABEL=all
      - AUTOHEAL_INTERVAL=15

组合效果:xray 进程僵死 → healthcheck 30 秒内发现 → 容器变 unhealthy → autoheal 15 秒内重启容器 → restart 策略兜底拉起。整个过程全自动,最长不到一分钟,用户几乎无感知。这是 2026 年自建网关「免维护」体验的核心三件套:Compose 声明 + Watchtower 更新 + healthcheck/autoheal 自愈。

自愈只能解决「进程级」故障。线路拥堵、晚高峰丢包这种「网络级」问题,自动化帮不上忙——该上 BBR 的上 BBR(参考 晚高峰变慢完全指南:TCP 拥塞控制与 BBR),该换线路的换线路。如果你的主要痛点是晚高峰速度而不是维护,可能根本不需要自建——自由猫 的 IEPL 专线(¥9/月起步)延迟表现接近自建 CN2 GIA,晚高峰稳定性反而更好,因为机场有大量节点可以自动切换。维护自动化解决「服务活着」,线路质量解决「速度快」,两件事别混为一谈。

六、日志与磁盘保护:防止隐形爆盘

自建 VPS 最常见的故障不是服务挂了,而是磁盘满了。日志、镜像、容器层文件会悄悄吃掉空间,一旦 / 分区 100%,Docker daemon 直接罢工,所有容器一起挂。防护分三层:

第一层是 Compose logging 限制(第二节已写):每个容器日志锁死在 30MB。第二层是 Watchtower 的 CLEANUP=true:更新后自动清理旧镜像。第三层是定时清理悬空资源:

# crontab 每周日凌晨 3 点清理
0 3 * * 0 docker system prune -af --volumes --filter "until=168h"

--filter "until=168h" 只删 7 天前的悬空镜像,保留最近可能回滚用的版本。想更优雅的话,可以把清理也容器化:

  docker-cleanup:
    image: docker:cli
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    entrypoint: /bin/sh
    command: -c "while true; do docker system prune -af --filter 'until=168h' >/dev/null 2>&1; sleep 86400; done"

再配合一个简单的磁盘告警(比如 crontab 里 df -h 超过 80% 发一封邮件或推送到 Telegram),磁盘问题基本就能在变成事故之前被发现。更完整的监控方案(cAdvisor 容器指标、流量统计、多节点热备)参考 自建 VPN 进阶管理:多节点热备、流量监控与成本优化。

七、故障自愈实战演练:验证你的网关真的「免维护」

配置完不等于万事大吉——建议搭好后做一轮破坏性演练,确认自动化真的在工作:

  1. kill 容器测试:docker kill xray,观察 5-10 秒内 restart 策略自动拉起,docker compose ps 显示 Up。
  2. 僵死测试:手动把 xray 进程 kill -STOP 挂起(模拟僵死),healthcheck 探测失败,30-60 秒内 autoheal 重启容器,恢复 Up (healthy)。
  3. 更新测试:手动 docker compose pull 触发一次版本变化,确认 Watchtower 能正常重建、新版本配置兼容。
  4. 重启测试:sudo reboot 重启 VPS,确认开机后所有容器按 depends_on 顺序自动就绪,无需人工干预。
  5. 回滚测试:如果某次自动更新后容器起不来(unhealthy 循环),手动 docker compose up -d --force-recreate 指定旧 tag 回滚,确认 compose 文件里改一行 tag 就能恢复。

演练全部通过,你的网关才算是真正进入了「免维护」状态。以后每周登录一次看 docker compose ps 全是 healthy,就够了。

八、自动化之后的真实成本账:自建 vs 机场

把自动更新和自愈都配好之后,自建网关的维护成本已经压到很低——每周最多 10 分钟看一眼状态。但成本账还要算全:VPS 月租(¥30-100)、线路质量波动、单点故障风险(VPS 被墙就全断)、以及「自建 IP 是机房 IP」这个绕不开的限制——流媒体解锁、跨境电商多账号这类对 IP 纯净度敏感的场景,机房 IP 天然吃亏。

如果你的核心需求是流媒体解锁、跨境电商多账号运营这种对 IP 质量要求高的场景,自建网关不一定是最优解——主打住宅 IP / 原生 IP 的机场就是为这类场景设计的,开箱即用,不用自己折腾 IP 纯净度(第二节提过这类选择)。

至于纯速度党:自建 + 好线路 VPS 的天花板确实高,但普通用户更常见的情况是「花了半天调优,晚高峰还是堵」。这种时候,把时间花在选一条好线路上,比继续折腾自建划算得多。

做最终决策前,把两边的账摊开看:

维度自建网关(Compose 自动化后)机场订阅
月度成本¥30-100(VPS)¥9-50
维护投入每周约 10 分钟看状态零维护
延迟上限看 VPS 线路,CN2 GIA 可到 50-80msIEPL 专线 50-100ms
IP 纯净度机房 IP,流媒体/多账号受限可选住宅 IP / 原生 IP
故障风险单点,VPS 被墙全断50+ 节点自动切换
可玩性高,完全可控低,即开即用

我的建议很直接:喜欢折腾、追求极致可控、有技术底子的,Compose 自动化这套值得搭,本文所有配置抄下来半小时就能跑通;不想维护、要稳定省心的,直接买机场——万达云 的中转 + IEPL 混合线路(¥16.8/月起步)覆盖多设备场景,把省下来的时间花在正事上,是 2026 年大多数人的理性选择。两者也不是非此即彼,机场 + 自建 VPS 混合架构 用机场做入口、VPS 做落地,兼顾稳定与可控,适合进阶玩家。

📖 推荐阅读