1. 为什么 Docker 一跑容器UFW 就像没开一样如果你在 Ubuntu 上用过 Docker UFW大概率遇到过这个场景明明ufw status里只放行了 22、80、443结果docker run -p 8080:80 nginx一执行外网直接就能访问 8080。防火墙规则看起来完全没拦住它。这不是 UFW 坏了而是 Docker 在启动容器时会往 iptables 的nat表和filter表里插入自己的转发规则这些规则的优先级在 UFW 的ufw-user-input链之前。数据包走的是 Docker 自己创建的DOCKER链压根没经过 UFW 的过滤路径。所以你在 UFW 里 deny 什么对-p发布的端口基本无效。我试过最直接的验证方式先ufw default deny incoming再ufw allow 22/tcp然后跑一个docker run --rm -p 80:80 nginx:alpine浏览器访问公网 IPnginx 欢迎页照样出来。这时候ufw status verbose显示 80 是 deny 的但服务就是暴露着。这个问题的核心矛盾在于Docker 需要 iptables 做端口映射和容器互通而 UFW 也依赖 iptables 做主机防火墙两者对链的插入顺序和规则归属没有协调机制。网上常见的“关闭 Docker iptables”方案{iptables: false}确实能让 UFW 重新管住端口但代价是容器访问外网、容器间通信、客户端 IP 获取都会出问题后面还得手动补一堆 iptables 规则维护成本反而更高。所以更合理的思路不是“关掉 Docker 的 iptables”而是“让容器不直接发布端口到公网”。具体做法是容器只用expose在内部网络暴露端口由 Traefik 作为唯一入口监听 80/443UFW 只需要放行 Traefik 的端口。这样 Docker 的 iptables 转发规则只存在于内部网卡之间公网入口收敛到 Traefik 这一个进程上UFW 的规则就能真正生效。这个方案还有一个附带好处客户端真实 IP 能通过 Traefik 的X-Forwarded-For传递到后端容器fail2ban 之类的工具可以继续用日志里也不会全是虚拟网卡地址。2. TaoToken 统一 Key 接入让 Traefik 后面的 AI 服务共用一套凭证在讲 Traefik 配置之前先说一下为什么这篇会提到 TaoToken。场景是这样的Traefik 后面往往不止一个 Web 服务可能还有几个调用大模型 API 的内部工具或 Agent 服务。如果每个服务各自维护一套 API Key轮换、限额、审计都会很麻烦。TaoToken 提供的是统一 Key 接入层你可以在它的控制台里生成一个 Key然后让 Traefik 后面的多个服务共用这个 Key 去调用模型接口省去每个服务单独配置的重复工作。TaoToken 的官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。如果你只是想让 Traefik 后面的某个服务快速验证模型调用可以直接用模型对话页面如果是长期跑编码类 Agent建议看 Coding Plan接入细节和 Key 管理在接入文档和 API Keys 页面里都有说明。这里要强调的是TaoToken 在这个架构里扮演的是“统一凭证出口”的角色不是替代 Traefik 的反向代理功能。Traefik 负责流量入口和路由TaoToken 负责模型调用的 Key 收敛。两者职责不冲突。3. 可复制的 Traefik 静态配置与 Docker Compose 骨架下面给出一份可以直接改域名就用的配置。思路是 Traefik 以容器方式运行但只发布 80 和 443 到宿主机其他后端容器一律不写ports只用expose。先建目录结构mkdir -p /data/traefik/{config,logs,ssl} touch /data/traefik/config/traefik.yml touch /data/traefik/config/dynamic.yml静态配置traefik.ymlglobal: sendAnonymousUsage: false log: level: INFO filePath: /logs/traefik.log accessLog: filePath: /logs/access.log api: dashboard: true insecure: false entryPoints: web: address: :80 http: redirections: entryPoint: to: websecure scheme: https websecure: address: :443 providers: docker: endpoint: unix:///var/run/docker.sock exposedByDefault: false watch: true file: filename: /config/dynamic.yml watch: true certificatesResolvers: le: acme: email: youremail.com storage: /ssl/acme.json httpChallenge: entryPoint: web动态配置dynamic.yml这里放中间件和 dashboard 路由http: middlewares: security-headers: headers: stsSeconds: 31536000 contentTypeNosniff: true browserXssFilter: true rate-limit: rateLimit: average: 100 burst: 50 routers: dashboard: rule: Host(traefik.your.com) entryPoints: - websecure service: apiinternal middlewares: - security-headers tls: certResolver: leDocker Compose 文件docker-compose.ymlversion: 3.8 services: traefik: image: traefik:v2.11 container_name: traefik restart: unless-stopped ports: - 80:80 - 443:443 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./config/traefik.yml:/etc/traefik/traefik.yml:ro - ./config/dynamic.yml:/config/dynamic.yml:ro - ./logs:/logs - ./ssl:/ssl networks: - proxy labels: - traefik.enabletrue whoami: image: traefik/whoami container_name: whoami restart: unless-stopped expose: - 80 networks: - proxy labels: - traefik.enabletrue - traefik.http.routers.whoami.ruleHost(who.your.com) - traefik.http.routers.whoami.entrypointswebsecure - traefik.http.routers.whoami.tls.certResolverle - traefik.http.services.whoami.loadbalancer.server.port80 networks: proxy: name: proxy注意 whoami 服务没有ports字段只有expose。这意味着它只在proxy这个 Docker 网络内部可达公网无法直接访问它的 80 端口。Traefik 通过 Docker socket 发现它并根据 label 自动生成路由。启动cd /data/traefik docker compose up -d4. UFW 放行验证与 iptables 链检查Traefik 起来之后UFW 只需要放行 80 和 443。先确认当前规则ufw status verbose如果之前没配过按下面这套来ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw enable启用后再次检查ufw status numbered应该看到 22、80、443 是 ALLOW其余默认 DENY。接下来验证 Docker 的 iptables 规则有没有绕过 UFW。查看 nat 表的 DOCKER 链iptables -t nat -L DOCKER -n --line-numbers正常情况你会看到只有 Traefik 容器的 80/443 映射规则以及 Docker 内部网络的 MASQUERADE 规则。如果看到 whoami 或其他后端容器的端口映射说明 compose 里写了ports需要改成expose。再看 filter 表的转发链iptables -L DOCKER-USER -n --line-numbersDOCKER-USER链是 Docker 留给用户自定义规则的位置优先级在 Docker 自动规则之前。如果你想额外限制某些源 IP 访问 Traefik可以在这里加规则比如只允许特定网段访问 443iptables -I DOCKER-USER -p tcp --dport 443 ! -s 10.0.0.0/8 -j DROP不过更推荐的做法是保持DOCKER-USER干净把访问控制交给 Traefik 的中间件比如 IPWhiteList或 UFW 的上游规则。验证端口暴露情况从外部机器执行curl -I http://who.your.com curl -I https://who.your.com如果 who.your.com 解析到这台服务器应该看到 301 跳转到 https然后 https 返回 200。再直接试后端容器的内部端口比如 whoami 的 80curl -I http://your-server-ip:80这个请求应该被 Traefik 处理返回 404 或跳转而不是直接命中 whoami。如果你用nmap扫一下服务器除了 22、80、443不应该看到其他开放端口。5. nginx 共存与常见报错排查5.1 nginx 已经占了 80/443 怎么办如果这台机器上已经有 nginx 在跑Traefik 再绑 80/443 会报bind: address already in use。两种处理方式第一种把 nginx 改成只监听 127.0.0.1:8080让 Traefik 反代它。nginx 配置里改server { listen 127.0.0.1:8080; server_name legacy.your.com; location / { proxy_pass http://127.0.0.1:3000; } }然后在 Traefik 的 dynamic.yml 里加一个 file provider 的路由http: routers: legacy: rule: Host(legacy.your.com) entryPoints: - websecure service: legacy-svc tls: certResolver: le services: legacy-svc: loadBalancer: servers: - url: http://127.0.0.1:8080第二种如果 nginx 只是做静态文件直接把它也容器化用expose接入 Traefik 网络彻底去掉宿主机上的 nginx 进程。5.2 常见报错对照报错信息原因处理port is already allocated80/443 被其他进程占用ss -tlnp | grep :80找到进程停掉或改端口Unable to obtain ACME certificate80 端口没放行或域名解析不对检查 UFW 80 规则、DNS A 记录404 page not found容器 label 没写对或 exposedByDefault 为 true 但没启用确认traefik.enabletrue和 rule 匹配Bad GatewayTraefik 找不到后端端口检查loadbalancer.server.port是否和容器 expose 一致permission denied /var/run/docker.sockTraefik 容器没权限读 socket挂载时加:ro或把容器加入 docker 组5.3 验证请求是否真的经过 Traefik用 whoami 镜像最直观。访问https://who.your.com返回内容里应该包含X-Forwarded-For: 你的公网IP X-Forwarded-Proto: https X-Real-Ip: 你的公网IP如果X-Forwarded-For显示的是172.x.x.x这种内网地址说明请求没经过 Traefik而是直接打到了容器。这时候回去检查 compose 里是不是误写了ports。另外Traefik 的 access log 在/data/traefik/logs/access.log每一条请求都会记录来源 IP、状态码、后端服务。你可以用这个日志配合 fail2ban 做扫描器拦截规则和 nginx 的日志格式类似改一下正则就能复用。6. 把入口收敛之后Key 和流量都各归其位这套方案跑下来最明显的变化是ufw status终于和实际暴露的端口一致了。公网只能看到 22、80、443后端容器全部藏在 Docker 网络里Traefik 根据 Host 和 Path 做路由。客户端 IP 通过X-Forwarded-For透传日志里不再是清一色的172.20.0.x。如果你后面要接入模型调用类的服务建议把 Key 统一收到 TaoToken 这边管理。API Keys 页面可以生成和轮换 Key接入文档里有不同语言的调用示例。Traefik 后面的服务只需要配置一个环境变量指向https://taotoken.net/api不用每个服务单独维护凭证。长期跑编码 Agent 的话Coding Plan 的额度模型比按次调用更划算具体可以在模型对话页面先试一下响应格式再决定。最后提醒一个容易忽略的点Traefik 的 dashboard 默认走apiinternal如果你在 dynamic.yml 里配了Host(traefik.your.com)记得给这个路由加上认证中间件比如 basicauth否则 dashboard 会直接暴露在公网上。basicauth 的密码用htpasswd -nb admin yourpassword生成填到 middlewares 里即可。