起因
服务器上跑了 8 个 Docker 容器,每个都想用自己的子域名访问 + HTTPS。
nginx 每加一个服务要改 conf + 申证书 + reload;Caddy 也要改 Caddyfile。
有没有"加新容器自动注册"的方案?
Traefik 是 Go 写的反代,原生支持"从 Docker / K8s / Consul 等动态发现
服务" + Let's Encrypt 自动签证书。加一个容器 = 加一组 label,零配置改动。
解决方案
1. Traefik 容器本身
# /srv/traefik/docker-compose.yml
services:
traefik:
image: traefik:v3.1
restart: unless-stopped
ports:
- "80:80"
- "443:443"
# Traefik dashboard (内网可见)
- "127.0.0.1:8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
- ./traefik.yml:/etc/traefik/traefik.yml:ro
networks:
- web
networks:
web:
external: true
创建共享网络:
docker network create web
2. traefik.yml
api:
dashboard: true
insecure: true # 仅 127.0.0.1 暴露所以可以 true
entryPoints:
web:
address: ':80'
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ':443'
providers:
docker:
exposedByDefault: false # 容器必须显式 enable 才被 routing
network: web
certificatesResolvers:
le:
acme:
email: [email protected]
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
# 想用 DNS-01 + Cloudflare:
# dnsChallenge:
# provider: cloudflare
# delayBeforeCheck: 0
log:
level: INFO
accessLog: {}
启动:
docker compose up -d
3. 加业务容器:只加 label,不改 traefik
/srv/myapp/docker-compose.yml:
services:
myapp:
image: myorg/myapp:latest
restart: unless-stopped
networks:
- web
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.myapp.rule=Host(`myapp.example.com`)'
- 'traefik.http.routers.myapp.entrypoints=websecure'
- 'traefik.http.routers.myapp.tls.certresolver=le'
- 'traefik.http.services.myapp.loadbalancer.server.port=3000'
networks:
web:
external: true
docker compose up -d
Traefik 一秒内:
- 发现新容器
- 加路由
myapp.example.com → myapp:3000 - 通过 HTTP-01 challenge 自动签 Let's Encrypt 证书
- 80 自动跳 443
curl https://myapp.example.com/ 立刻可用。
4. 多服务批量
services:
api:
image: api:latest
networks: [web]
labels:
- traefik.enable=true
- traefik.http.routers.api.rule=Host(`api.example.com`)
- traefik.http.routers.api.entrypoints=websecure
- traefik.http.routers.api.tls.certresolver=le
- traefik.http.services.api.loadbalancer.server.port=8000
blog:
image: ghost:5
networks: [web]
labels:
- traefik.enable=true
- traefik.http.routers.blog.rule=Host(`blog.example.com`)
- traefik.http.routers.blog.entrypoints=websecure
- traefik.http.routers.blog.tls.certresolver=le
- traefik.http.services.blog.loadbalancer.server.port=2368
uptime:
image: louislam/uptime-kuma:1
networks: [web]
labels:
- traefik.enable=true
- traefik.http.routers.up.rule=Host(`up.example.com`)
- traefik.http.routers.up.entrypoints=websecure
- traefik.http.routers.up.tls.certresolver=le
- traefik.http.services.up.loadbalancer.server.port=3001
3 个服务,3 套 label,3 个域名。零修改 traefik 配置。
5. 加中间件:basic auth / IP 白名单 / rate limit
label 里声明 + 应用:
labels:
- traefik.enable=true
- traefik.http.routers.admin.rule=Host(`admin.example.com`)
- traefik.http.routers.admin.entrypoints=websecure
- traefik.http.routers.admin.tls.certresolver=le
- traefik.http.routers.admin.middlewares=auth,ratelimit
- 'traefik.http.middlewares.auth.basicauth.users=alice:$$apr1$$abc...'
- 'traefik.http.middlewares.ratelimit.ratelimit.average=10'
- 'traefik.http.middlewares.ratelimit.ratelimit.burst=20'
- traefik.http.services.admin.loadbalancer.server.port=8000
basic auth password 用 htpasswd -nb alice secret 生成(注意 $ 在
YAML 里 escape 成 $$)。
6. dashboard
打开 SSH tunnel:
ssh -L 8080:localhost:8080 server
# 浏览器:http://localhost:8080
看到所有 router / service / middleware,状态可视化。
效果
- 加新容器从"改 nginx + certbot + reload + DNS 配 + …" 收敛到
"docker-compose up,加 5 行 label" - 8 个服务的反代配置文件总长 < 100 行(之前 nginx 200+ 行 / 4 个
vhost 文件) - 证书自动续期,不再有 "忘续期 → 网站红框" 事故
- 加 / 删服务无 traefik 重启
- dashboard 一眼看到所有 route 状态
与 nginx / Caddy / HAProxy 对比
| Traefik | nginx | Caddy | HAProxy | |
|---|---|---|---|---|
| Docker 自动发现 | ✅ 原生 | ❌(需第三方) | 一定程度 | ❌ |
| 自动 HTTPS | ✅ | ❌(需 certbot) | ✅ | ❌ |
| K8s ingress | ✅ | ✅ | ✅ | ✅ |
| 性能 | 中 | 极高 | 高 | 极高 |
| 配置 | YAML / labels | nginx 语法 | Caddyfile | HAProxy 语法 |
| 学习曲线 | 中 | 高 | 低 | 高 |
容器化场景选 Traefik;静态站 / 极致性能选 nginx;混合简单场景 Caddy。
踩过的坑
-
network: web不指定 Traefik 不知道连哪个 net:容器在多个网络
时 Traefik 不确定走哪个。一定在 traefik.yml 里providers.docker.network: web
或每个服务 label 写traefik.docker.network=web。 -
acme.json 权限不对:Traefik 要求 600。
chmod 600 letsencrypt/acme.json。 -
HTTP-01 challenge 失败:80 端口被占 / 路由器没转发。检查
curl -I http://myapp.example.com/.well-known/acme-challenge/test
能不能到 Traefik 容器。 -
生产 dashboard 暴露公网:
api.insecure=true千万别开公网。
要外网访问 dashboard 加 router + auth middleware 包起来。 -
Let's Encrypt 频率限制:测试期间反复重启 + 反复申证书会被
LE rate limit。dev 用acme.caServer: https://acme-staging-v02.api.letsencrypt.org/directory
测试,跑通后切回生产 CA。
登录后参与评论。