Traefik 配合 Docker 暴露服务实战:从第一个 HTTP 服务到路径路由与 TLS 加密
本文以 Traefik 仓库中的官方入门指南(expose/docker/basic.md)为主体,完整讲解如何在 Docker 环境中让 Traefik 通过容器标签自动发现并暴露 HTTP 服务:从搭建第一个 whoami 路由、按 URL 路径分流到不同后端,再到为服务启用 TLS 终止。读完本文,你将掌握 traefik.http.routers.* 标签的编写方式、Docker provider 的动态配置生成机制,以及结合 File provider 挂载自签证书的完整 HTTPS 部署方案。
前置条件
开始前需要满足以下条件:
- 已安装 Docker 与 Docker Compose;
- 对 Docker 网络(network)与标签(labels)有基本了解;
- Traefik 已通过 Traefik Docker 部署指南完成基础部署(本文会给出完整的自包含 Compose 文件,也可独立运行)。
暴露你的第一个 HTTP 服务
Traefik 仓库配套提供了一个 traefik/whoami 镜像,它会把收到的请求头、IP、来源地址原样回显,是验证反向代理路由结果最直观的工具。下面用它演示一次完整的基础路由。
编写 docker-compose.yml
创建 docker-compose.yml 文件,定义 Traefik 与 whoami 两个服务:
services:
traefik:
image: "traefik:v3.4"
container_name: "traefik"
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--entryPoints.web.address=:80"
ports:
- "80:80"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
whoami:
image: "traefik/whoami"
restart: unless-stopped
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"
- "traefik.http.routers.whoami.entrypoints=web"
networks:
proxy:
name: proxy
各关键配置项的含义如下:
| 配置项 | 作用 |
|---|---|
--providers.docker=true |
启用 Docker provider,让 Traefik 通过 Docker API 发现容器 |
--providers.docker.exposedbydefault=false |
默认不暴露任何容器,只有带 traefik.enable=true 标签的容器才会生成路由 |
--providers.docker.network=proxy |
指定 Traefik 使用哪个网络中的容器 IP 来访问后端(见下文源码分析) |
--entryPoints.web.address=:80 |
定义名为 web 的 HTTP 入口,监听 80 端口 |
/var/run/docker.sock 只读挂载 |
提供 Docker API 访问通道(:ro 保证只读) |
traefik.http.routers.whoami.rule |
路由匹配规则:Host(\whoami.docker.localhost`)` 表示 Host 为该域名时命中 |
traefik.http.routers.whoami.entrypoints=web |
该路由绑定到 web 入口 |
保存文件后启动服务:
docker compose up -d
验证路由生效
服务此时应当可以通过 http://whoami.docker.localhost/ 访问。由于该域名未必在本地解析,可以手动指定 Host 头测试:
curl -H "Host: whoami.docker.localhost" http://localhost/
预期输出类似:
Hostname: whoami
IP: 127.0.0.1
IP: ::1
IP: 172.18.0.3
IP: fe80::215:5dff:fe00:c9e
RemoteAddr: 172.18.0.2:55108
GET / HTTP/1.1
Host: whoami.docker.localhost
User-Agent: curl/7.68.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 172.18.0.1
X-Forwarded-Host: whoami.docker.localhost
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: 5789f594e7d5
X-Real-Ip: 172.18.0.1
响应中出现的 X-Forwarded-Host、X-Forwarded-Proto、X-Real-Ip 等请求头说明请求确实经过了 Traefik 的反向代理层——它向 whoami 转发了请求并附加了标准的转发头。
源码级原理:Docker provider 如何把标签变成路由
上面的效果在 Traefik 源码中由 Docker provider 完成,核心实现位于 pkg/provider/docker/pdocker.go:
- 首次全量同步:provider 启动后调用
listContainers列出全部容器,经builder.build生成动态配置,推送到configurationChan通道(见 pdocker.go#L82-L92)。 - 事件驱动增量更新:当
watch开启(默认开启)时,Traefik 订阅容器事件流,收到start、die或health_status前缀的事件时重新构建配置(见 pdocker.go#L94-L136)。这也是为什么新起的 whoami 容器无需重启 Traefik 就能被自动发现。 - 容器过滤:pkg/provider/docker/config.go 的
keepContainer实现了本文exposedbydefault=false的行为——若容器没有traefik.enable=true标签,ExtraConf.Enable为false,容器直接被过滤(见 config.go#L197-L234)。traefik.enable的默认值即 provider 的ExposedByDefault配置(默认true,见 shared_labels.go#L42-L63)。同一函数还表明:非 running 状态或 healthcheck 不健康的容器会被过滤,其 service 保留空负载列表(可用allowEmptyServices调整)。 - 后端地址解析:Traefik 用
--providers.docker.network=proxy指定的网络(proxy)来取容器 IP——getIPAddress优先从该网络中查找network.Addr,找不到会降级到第一个可用网络并打警告日志(见 config.go#L359-L425)。这就是为什么 whoami 必须和 traefik 容器接入同一个proxy网络。 - 端口探测:若没有用
traefik.http.services.<name>.loadbalancer.server.port标签指定端口,Traefik 取容器暴露的最低端口(见 shared.go#L189-L207);服务名则默认取自 Compose 注入的com.docker.compose.service标签,格式为服务名_项目名(见 shared.go#L209-L217)。
关于 Docker provider 的更多安装配置选项(如 endpoint、constraints、useBindPortIP),可查阅 Docker provider 参考文档;可使用的完整标签清单见 Docker 路由标签参考。
添加路由规则:基于路径分流
接下来按 URL 路径将流量导向不同服务——这在 API 版本化、前后端分离、微服务分组中非常实用。在 docker-compose.yml 中追加一个 whoami-api 服务:
# ...
# New service
whoami-api:
image: "traefik/whoami"
networks:
- proxy
container_name: "whoami-api"
environment:
- WHOAMI_NAME=API Service
labels:
- "traefik.enable=true"
# Path-based routing
- "traefik.http.routers.whoami-api.rule=Host(`whoami.docker.localhost`) && PathPrefix(`/api`)"
- "traefik.http.routers.whoami-api.entrypoints=web"
两个路由同时命中 whoami.docker.localhost 这个 Host,为什么 /api 请求会走 whoami-api?关键在于 Traefik 的路由优先级机制:默认情况下优先级等于规则字符串的长度,更长的规则更具体、优先级更高(priority 显式设置为 0 时才回到按长度排序)。本文的 whoami-api 规则(Host(...) && PathPrefix(/api))比 whoami 规则长,因此 /api 前缀的请求优先命中 whoami-api,其余路径落到 whoami。更完整的匹配器(Path/PathPrefix/PathRegexp、Host/HostRegexp、Query、Header、Method 等)与优先级计算细节,参见 路由规则与优先级参考。
应用变更:
docker compose up -d
测试路径路由
验证不同路径是否落到不同服务:
# Root path should go to the main whoami service
curl -H "Host: whoami.docker.localhost" http://localhost/
# /api path should go to the whoami-api service
curl -H "Host: whoami.docker.localhost" http://localhost/api
对 /api 的请求,whoami 会在环境变量部分回显 WHOAMI_NAME=API Service,证明路径分流工作正常。
启用 TLS:自签证书 + File provider 动态配置
下面为服务启用 HTTPS。本地开发环境先使用自签证书;生产环境则应换成可信 CA 签发的证书(例如 Let's Encrypt,见 进阶指南)。
生成自签证书
mkdir -p certs
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout certs/local.key -out certs/local.crt \
-subj "/CN=*.docker.localhost"
*.docker.localhost 的通配符 CN 可以覆盖 whoami.docker.localhost、dashboard.docker.localhost 等子域(注意通配只匹配一层子域)。
然后创建动态配置目录,写入 TLS 证书配置:
mkdir -p dynamic
cat > dynamic/tls.yml << EOF
tls:
certificates:
- certFile: /certs/local.crt
keyFile: /certs/local.key
EOF
这里 certFile/keyFile 指向的是 容器内路径(/certs/...),与下面的 volume 挂载目标一致。
更新 docker-compose.yml 启用 HTTPS
services:
traefik:
image: "traefik:v3.4"
container_name: "traefik"
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
command:
- "--api.insecure=false"
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--providers.file.directory=/etc/traefik/dynamic"
- "--entryPoints.web.address=:80"
- "--entryPoints.websecure.address=:443"
- "--entryPoints.websecure.http.tls=true"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
# Add the following volumes
- "./certs:/certs:ro"
- "./dynamic:/etc/traefik/dynamic:ro"
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`dashboard.docker.localhost`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.service=api@internal"
# Add the following label
- "traefik.http.routers.dashboard.tls=true"
whoami:
image: "traefik/whoami"
restart: unless-stopped
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
# Add the following label
- "traefik.http.routers.whoami.tls=true"
whoami-api:
image: "traefik/whoami"
container_name: "whoami-api"
restart: unless-stopped
networks:
- proxy
environment:
- WHOAMI_NAME=API Service
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami-api.rule=Host(`whoami.docker.localhost`) && PathPrefix(`/api`)"
- "traefik.http.routers.whoami-api.entrypoints=websecure"
# Add the following label
- "traefik.http.routers.whoami-api.tls=true"
networks:
proxy:
name: proxy
与 HTTP 版本相比,关键变化有:
- 新增
websecure入口:--entryPoints.websecure.address=:443,并通过--entryPoints.websecure.http.tls=true强制该入口所有 HTTP 流量走 TLS; - File provider 加载动态 TLS 配置:
--providers.file.directory=/etc/traefik/dynamic对应挂载的./dynamic目录,dynamic/tls.yml中的证书被注册进 TLS store; - 各路由迁移到
websecure入口并加tls=true:traefik.http.routers.<name>.tls=true是路由级开关,声明该路由需要 TLS 终止,Traefik 会按 SNI 从 TLS store 中匹配证书; - Dashboard 自路由:Traefik 容器自身也打了
traefik.enable=true标签,通过service=api@internal把仪表盘挂在dashboard.docker.localhost上;配合--api.insecure=false,仪表盘不会裸露在 8080 管理端口,只能通过这条受 TLS 保护的 HTTPS 路由访问。
应用变更:
docker compose up -d
现在浏览器访问 https://whoami.docker.localhost/ 即可打开服务,自签证书会触发安全警告,接受后即可正常访问。
小结与进阶方向
本文完整走通了 Traefik + Docker 的三条基本能力链:标签驱动的容器发现(Docker provider 事件监听与 traefik.enable 过滤)、规则路由(Host + PathPrefix 组合与基于规则长度的优先级仲裁)、TLS 终止(websecure.http.tls=true 入口级强制 + 路由级 tls=true + File provider 证书配置)。三者叠加后,一个 Docker Compose 文件即可承载多域名、多路径、加密的反向代理。
掌握了这些基础之后,可以继续探索 进阶指南:
- 使用 middlewares 实现安全响应头与访问控制;
- 使用 Let's Encrypt(ACME)自动签发与续期证书;
- 为有状态应用配置 sticky sessions;
- 搭建基于认证结果的多层路由。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
