ByteNoteByteNote
Cloudflare Tunnel 容器化:不开端口上公网
字

字节笔记本

2026年10月7日 · 约 10 分钟读完

Cloudflare Tunnel 容器化:不开端口上公网

API中转
¥120

把一台 Homelab、一块开发板或者某个只监听 localhost 的调试服务暴露到公网,老办法无非两条:在路由器上做端口转发,或者租一台有公网 IP 的 VPS 跑 frp 之类的中转。两条路都绕不开同一件事——在防火墙上凿一个入站口,然后日夜守着它。Cloudflare Tunnel 换了一种思路:入站端口一个不开,由服务器主动向 Cloudflare 边缘网络发起出站连接,公网流量沿着这条已经建立好的隧道反向流回来。这篇文章记录一次完整的部署过程:先用命令行六步把隧道跑通,再把它容器化,变成一条 docker compose up -d 就能常驻运行的守护服务。

为什么不开端口反而更安全

传统端口转发的问题不只是配置麻烦。公网 IP 直接暴露意味着端口扫描、爆破、漏洞探测全天候上门;家宽动态 IP 还得配 DDNS;HTTPS 证书要自己申请续期。frp 这类自建中转方案虽然把服务藏在了 VPS 后面,但 VPS 本身就是一个需要维护的公网节点,带宽和安全都得自己兜底。

Cloudflare Tunnel 的架构正好绕开这些点。本地的 cloudflared 客户端启动后,会与 Cloudflare 边缘节点建立多条持久的出站加密连接(基于 QUIC,失败时回落 HTTP/2),隧道凭证只在本地保存,服务端没有任何可供扫描的入口。外部用户访问你绑定的域名时,请求先落到 Cloudflare 边缘,再经隧道转发到内网服务。顺带白拿了三样东西:全球边缘网络的 DDoS 清洗、自动签发续期的 SSL 证书,以及可选的访问控制(Cloudflare Access 可以在应用前面再加一道身份验证,适合把管理后台只留给自己的场景)。

有一点需要留意:隧道本身无入口可扫,但唯一的敏感物转移到了本地——登录生成的证书和隧道凭证 JSON 等同于隧道的完全使用权,泄露等于把内网入口拱手让人,务必限制文件权限,不要提交进代码仓库。

和同类工具放在一起看更清楚:ngrok 开箱即用,但免费版随机域名、隧道随会话销毁,更适合临时演示;frp 完全自控,前提是你愿意养一台公网 VPS;Tailscale 是私有组网,设备之间互通,但并不面向公网访客。如果诉求是「让任何浏览器都能访问某个内网服务,同时尽量少暴露自己」,Cloudflare Tunnel 是目前成本和安全性平衡得最好的方案,免费额度对个人使用足够。

命令行六步:先把隧道跑通

容器化之前,值得先在命令行里完整走一遍流程,理解隧道的各个组成部分。

第一步,在服务器上安装 cloudflared 客户端,从 Cloudflare 官网按操作系统下载对应版本即可。第二步,登录账户:

bash
cloudflared tunnel login

第三步,创建一条命名隧道:

bash
cloudflared tunnel create <隧道名称>

创建成功后会生成一个隧道 ID 和对应的凭证 JSON 文件。第四步,写一个 config.yml,声明要把流量送到的本地服务:

yaml
url: http://localhost:8000
tunnel: <你的隧道ID>
credentials-file: /path/to/your/tunnel/credentials/file.json

第五步,把域名路由到隧道——本质上是在 Cloudflare DNS 里自动建一条指向隧道的 CNAME 记录:

bash
cloudflared tunnel route dns <隧道名称> <你的域名>

第六步,运行:

bash
cloudflared tunnel run <隧道名称>

浏览器里打开域名,内网服务就出现在公网了。整个过程里防火墙始终没有新开的入站规则。

命令行六步部署 Cloudflare Tunnel

把它装进 Docker Compose

手动跑通的隧道有几个隐患:cloudflared 挂在某个终端会话或 systemd 服务上,机器重启后要人肉拉起;配置文件、凭证散落在用户目录里;换一台机器部署得重走一遍流程。把它容器化之后,这些问题一起消失——镜像官方维护、配置进版本库、重启策略交给 Docker。

官方镜像就是 cloudflare/cloudflared,一份最小可用的 docker-compose.yml 长这样:

yaml
version: '3'

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel run
    environment:
      - TUNNEL_TOKEN=${TUNNEL_TOKEN}
    volumes:
      - /path/to/your/config:/etc/cloudflared
    networks:
      - web

networks:
  web:
    external: true

几个关键配置值得展开说:

认证方式换成了 TUNNEL_TOKEN。 上面命令行流程用的是「本地管理」模式——凭证文件和 config.yml 都在本地,隧道的路由规则由这台机器自己解释。而 token 是「远程管理」模式:在 Cloudflare 控制台创建隧道时直接生成一段 token,写入 .env 文件或环境变量,隧道的 ingress 规则、域名绑定全部在网页上配置,本地连配置文件都不用挂。两种模式二选一即可,token 模式对多机部署和改配置的场景明显更友好——改一条路由规则刷新网页就行,不用登服务器。compose 里的 ${TUNNEL_TOKEN} 会从同目录的 .env 读取,这个文件记得加进 .gitignore。

restart: unless-stopped 是常驻的关键。 容器崩溃或宿主机重启后都会自动拉起,等效于把 systemd 守护的活交给了 Docker。除非你手动 stop,否则它一直在。

external 网络 web 是容器互通的桥梁。 声明为 external 意味着这个网络需要提前存在(docker network create web),其他同样挂在 web 网络上的服务容器——比如你的 Nginx、应用容器——都能被 cloudflared 直接按服务名访问,不需要往宿主机暴露任何端口。多个项目可以共享这一个入口网络。

docker compose up -d 之后,docker logs -f cloudflared 里看到连接注册成功的日志,隧道就活了。

Docker Compose 容器化部署架构

踩坑提示

容器里的 localhost 不是你的机器。 这是最常见的一个坑:如果沿用本地模式的 config.yml 写 url: http://localhost:8000,在容器内 localhost 指向容器自己,隧道会把请求打回容器内部然后连接被拒。要么像上面那样把 cloudflared 和目标服务放进同一个 Docker 网络改用服务名,要么把 url 指到宿主机的内网地址。同理,credentials-file 挂载路径必须与容器内路径 /etc/cloudflared 对齐,而不是宿主机上的原始路径。

token 模式别再挂 config.yml。 两种认证模式混着用,cloudflared 启动时会报错或者行为不可预期。用 token 就删掉 volumes,用凭证文件就去掉 TUNNEL_TOKEN 环境变量。

版本别盲目 latest。 示例为了简洁用了 latest 标签,生产环境建议钉住具体版本号,升级前看一下 Cloudflare 的 release notes,避免隧道组件与服务端协议不兼容导致断连。

DNS 记录冲突。 route dns 报错说记录已存在时,多半是域名下已有同名的 A/CNAME 记录,先删旧记录再执行,或者直接在控制台的隧道页面里补绑域名。

日志是第一排查入口。 隧道连不上时先看 docker logs cloudflared:token 无效、出站流量被防火墙拦截、QUIC 被中间设备干扰,日志都会写明原因。个别网络环境会对 UDP 限速,给启动命令追加协议参数强制回落到 HTTP/2 即可恢复。

什么场景值得上

梳理下来,四类场景收益最高:Homelab 服务的外部访问(NAS 面板、Home Assistant、自建相册),尤其是没有公网 IP 的家宽,端口转发这条路原本就走不通;开发环境的临时预览,把链接甩给同事或客户远程验收;CI/CD 流水线里的预览部署,每次构建起一条短命隧道;以及小型生产环境的流量入口,替代「VPS + Nginx + certbot」这套传统组合。反过来说,如果你只在内网设备之间传输数据,Tailscale 的私有组网更合适;如果需要点对点大流量直连,隧道绕行边缘节点反而增加延迟。

隧道跑通只是第一步。真正让它从「能用」变成「放心用」的,是后面那几步:用 Cloudflare Access 给敏感服务套上身份验证、在 Zero Trust 面板里审计访问日志、把 compose 文件和 token 管理纳入部署脚本。内网服务上公网这件事,最怕的从来不是配置繁琐,而是忘了自己暴露了什么。

相关文章

分享: