云服务器配置 Traefik 教程
Traefik 适合用来做云服务器上的统一入口:所有 Web 服务都只需要加入同一个 Docker 网络,然后通过容器 label 声明域名、端口、HTTPS 证书策略即可。
本文默认场景:
- 云服务器已经有公网 IP。
- 域名已经解析到这台服务器。
- 使用 Docker Compose 部署 Traefik。
- 使用 Let’s Encrypt 自动签发 HTTPS 证书。
- 服务都以 Docker 容器方式运行。
1. 准备服务器
先登录服务器:
ssh root@你的服务器IP更新系统:
apt update && apt upgrade -y安装常用工具:
apt install -y curl vim ufw apache2-utils如果没有安装 Docker,可以使用官方安装脚本:
curl -fsSL https://get.docker.com | sh确认 Docker 和 Compose 可用:
docker version
docker compose version2. 配置防火墙
开放 SSH、HTTP、HTTPS:
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw statusWarning
不要把 Traefik Dashboard 的
8080端口直接开放到公网。生产环境应该通过域名、HTTPS 和 Basic Auth 访问 Dashboard。
3. 准备域名解析
在 DNS 服务商中添加 A 记录:
| 记录名 | 类型 | 值 |
|---|---|---|
traefik.example.com | A | 服务器公网 IP |
whoami.example.com | A | 服务器公网 IP |
把上面的 example.com 替换成自己的域名。
验证解析是否生效:
dig traefik.example.com +short
dig whoami.example.com +short返回服务器公网 IP 即可继续。
3.1 接入 Cloudflare
如果域名托管在 Cloudflare,推荐先把域名接入 Cloudflare,再由 Cloudflare 管理 DNS 记录。
基本流程:
- 登录 Cloudflare Dashboard。
- 点击
Add a domain,输入自己的根域名,例如example.com。 - 选择免费套餐或需要的套餐。
- Cloudflare 会扫描现有 DNS 记录,确认 A 记录是否正确。
- 到域名注册商后台,把 NS 服务器改成 Cloudflare 给出的两个 nameserver。
- 等待 Cloudflare 显示域名状态为
Active。
DNS 记录建议:
| 记录名 | 类型 | 值 | Proxy status |
|---|---|---|---|
traefik | A | 服务器公网 IP | DNS only 或 Proxied |
whoami | A | 服务器公网 IP | DNS only 或 Proxied |
app | A | 服务器公网 IP | DNS only 或 Proxied |
Tip
如果使用 HTTP-01 证书挑战,调试阶段建议先把 Cloudflare 记录设为
DNS only。如果使用 DNS-01 证书挑战,可以使用Proxied,也可以申请泛域名证书。
Cloudflare 的 SSL/TLS 模式建议设置为:
SSL/TLS -> Overview -> Full (strict)Full (strict) 要求源站也有有效证书,正好由 Traefik + Let’s Encrypt 提供。
4. 创建目录
mkdir -p /opt/traefik/letsencrypt
cd /opt/traefik
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.jsonacme.json 用来保存 Let’s Encrypt 证书。权限必须收紧,否则 Traefik 可能拒绝使用。
5. 创建外部 Docker 网络
docker network create traefik之后需要被 Traefik 代理的业务容器,都加入这个网络。
6. 生成 Dashboard 登录密码
生成 Basic Auth 账号密码:
htpasswd -nbB admin '换成你的强密码'命令会输出类似:
admin:$2y$05$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxDocker Compose 的 label 中 $ 需要转义成 $$,例如:
admin:$$2y$$05$$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx7. 编写 Traefik 配置
7.1 创建 .env 文件
先在 /opt/traefik/.env 中保存公共参数:
TRAEFIK_IMAGE=traefik:v3.6
TRAEFIK_DOMAIN=traefik.example.com
ACME_EMAIL=你的邮箱@example.com
TRAEFIK_BASIC_AUTH_USERS='admin:$2y$05$替换成你生成的密码哈希'如果要使用 Cloudflare DNS-01 挑战,再加上:
CF_DNS_API_TOKEN=替换成你的Cloudflare_API_Token.env 文件里不要提交真实 token,也不要公开贴到笔记、仓库或群聊里。服务器上建议收紧权限:
chmod 600 /opt/traefik/.envWarning
Basic Auth 密码哈希如果直接写在
docker-compose.yml的 label 中,$需要写成$$。如果放在.env且用单引号包起来,可以保留admin:$2y$...这种原始格式。
7.2 HTTP-01 版本
在 /opt/traefik/docker-compose.yml 中写入:
services:
traefik:
image: ${TRAEFIK_IMAGE}
container_name: traefik
restart: unless-stopped
env_file:
- .env
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.ssh.address=:2222"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
- "2222:2222"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN}`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.middlewares=traefik-auth"
- "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS}"
networks:
traefik:
external: true需要替换的内容:
.env中的TRAEFIK_DOMAIN:换成 Dashboard 使用的域名。.env中的ACME_EMAIL:换成真实邮箱,Let’s Encrypt 会用它发送证书相关通知。.env中的TRAEFIK_BASIC_AUTH_USERS:换成上一步生成的 Basic Auth 字符串。
Tip
官方文档建议使用固定版本镜像,而不是
latest。这样升级 Traefik 时更可控。
7.3 Cloudflare DNS-01 版本
如果域名在 Cloudflare,推荐使用 DNS-01。优点:
- 不依赖 80 端口完成证书验证。
- 可以在 Cloudflare 开启橙云代理。
- 可以申请泛域名证书,例如
*.example.com。
先在 Cloudflare 创建 API Token:
- 进入 Cloudflare API Tokens。
- 点击
Create Token。 - 可以选择
Edit zone DNS模板,或自定义 token。 - 权限至少包含:
Zone / Zone / ReadZone / DNS / Edit
- Zone Resources 限制到需要管理的域名,例如
example.com。 - 创建后复制 token,写入
/opt/traefik/.env的CF_DNS_API_TOKEN。
把 docker-compose.yml 中 ACME challenge 相关命令替换成 DNS-01:
services:
traefik:
image: ${TRAEFIK_IMAGE}
container_name: traefik
restart: unless-stopped
env_file:
- .env
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.ssh.address=:2222"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
ports:
- "80:80"
- "443:443"
- "2222:2222"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN}`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.middlewares=traefik-auth"
- "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS}"
networks:
traefik:
external: true如果要给某个服务申请泛域名证书,可以在对应 router 上加:
labels:
- "traefik.http.routers.app.tls.domains[0].main=example.com"
- "traefik.http.routers.app.tls.domains[0].sans=*.example.com"Note
Traefik 的 DNS-01 能力底层依赖 Lego。Cloudflare provider 支持
CF_DNS_API_TOKEN,也支持把 Zone Read 和 DNS Edit 拆成CF_ZONE_API_TOKEN与CF_DNS_API_TOKEN两个更细权限的 token。
8. 启动 Traefik
cd /opt/traefik
docker compose up -d
docker compose logs -f traefik看到 Traefik 正常启动后,访问:
https://traefik.example.com/dashboard/如果可以看到登录框和 Dashboard,说明入口代理已经配置成功。
9. 部署一个测试服务
创建 /opt/traefik/whoami.yml:
services:
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
networks:
traefik:
external: true启动测试服务:
docker compose -f /opt/traefik/whoami.yml up -d验证:
curl -I https://whoami.example.com返回 HTTP/2 200 或 HTTP/1.1 200 OK 就说明 HTTPS 代理正常。
10. 接入自己的业务服务
业务服务的关键点只有三个:
- 加入
traefik外部网络。 - 设置
traefik.enable=true。 - 用 label 声明域名、入口、证书解析器、容器内部端口。
示例:
services:
app:
image: your-app:latest
restart: unless-stopped
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
- "traefik.http.services.app.loadbalancer.server.port=8080"
networks:
traefik:
external: true其中 loadbalancer.server.port=8080 指的是容器内部监听端口,不是宿主机端口。接入 Traefik 后,业务容器通常不需要再写:
ports:
- "8080:8080"除非这个端口确实需要绕过 Traefik 直接暴露。
11. 常见服务接入模板
11.1 普通 Web 服务
适合 Spring Boot、Node.js、Nginx、前端静态站点、管理后台等普通 HTTP 服务。
services:
app:
image: your-app:latest
restart: unless-stopped
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
- "traefik.http.services.app.loadbalancer.server.port=8080"
networks:
traefik:
external: true11.2 文件上传服务
Traefik 默认不会像 Nginx 那样用 client_max_body_size 限制上传大小。真正的上传大小限制通常在业务应用、Nginx、Spring Boot、网关、对象存储 SDK 或 Cloudflare 上。
如果希望在 Traefik 层主动限制上传大小,可以使用 buffering middleware:
services:
file-api:
image: your-file-api:latest
restart: unless-stopped
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.file-api.rule=Host(`file.example.com`)"
- "traefik.http.routers.file-api.entrypoints=websecure"
- "traefik.http.routers.file-api.tls.certresolver=letsencrypt"
- "traefik.http.routers.file-api.middlewares=file-upload-limit"
- "traefik.http.middlewares.file-upload-limit.buffering.maxRequestBodyBytes=104857600"
- "traefik.http.middlewares.file-upload-limit.buffering.memRequestBodyBytes=10485760"
- "traefik.http.services.file-api.loadbalancer.server.port=8080"
networks:
traefik:
external: true上面配置含义:
maxRequestBodyBytes=104857600:最大上传请求体 100 MB。memRequestBodyBytes=10485760:超过 10 MB 后不全部放内存。
Warning
如果 Cloudflare 开了橙云代理,还要注意 Cloudflare 自身的上传大小限制。大文件上传场景更推荐客户端直传对象存储,例如 S3、R2、OSS,再由业务服务保存文件 URL。
11.3 文件下载服务
普通下载服务可以直接用 HTTP router。下载文件比较大时,重点是避免在应用层一次性把文件读入内存。
services:
download:
image: nginx:alpine
restart: unless-stopped
volumes:
- ./downloads:/usr/share/nginx/html:ro
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.download.rule=Host(`download.example.com`)"
- "traefik.http.routers.download.entrypoints=websecure"
- "traefik.http.routers.download.tls.certresolver=letsencrypt"
- "traefik.http.services.download.loadbalancer.server.port=80"
networks:
traefik:
external: true如果需要限制下载响应大小,也可以使用 buffering.maxResponseBodyBytes:
labels:
- "traefik.http.routers.download.middlewares=download-limit"
- "traefik.http.middlewares.download-limit.buffering.maxResponseBodyBytes=524288000"Tip
大文件下载不建议随便设置
maxResponseBodyBytes,否则超过限制会被 Traefik 拒绝。更多时候应该把限制放在业务鉴权、对象存储签名 URL 或 CDN 规则里。
11.4 WebSocket 服务
Traefik 会自动处理 WebSocket 的 HTTP Upgrade,一般不需要额外配置 Upgrade 或 Connection 请求头。
services:
ws-app:
image: your-ws-app:latest
restart: unless-stopped
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.ws-app.rule=Host(`ws.example.com`)"
- "traefik.http.routers.ws-app.entrypoints=websecure"
- "traefik.http.routers.ws-app.tls.certresolver=letsencrypt"
- "traefik.http.services.ws-app.loadbalancer.server.port=3000"
networks:
traefik:
external: true客户端连接示例:
const socket = new WebSocket("wss://ws.example.com/socket");如果 WebSocket 和普通 API 在同一个域名下,可以按路径拆路由:
labels:
- "traefik.http.routers.app-api.rule=Host(`app.example.com`) && PathPrefix(`/api`)"
- "traefik.http.routers.app-api.entrypoints=websecure"
- "traefik.http.routers.app-api.tls.certresolver=letsencrypt"
- "traefik.http.routers.app-api.service=app-api"
- "traefik.http.services.app-api.loadbalancer.server.port=8080"
- "traefik.http.routers.app-ws.rule=Host(`app.example.com`) && PathPrefix(`/socket`)"
- "traefik.http.routers.app-ws.entrypoints=websecure"
- "traefik.http.routers.app-ws.tls.certresolver=letsencrypt"
- "traefik.http.routers.app-ws.service=app-ws"
- "traefik.http.services.app-ws.loadbalancer.server.port=3000"Note
Cloudflare 代理 WebSocket 时也需要在 Cloudflare 侧允许 WebSocket。大多数账号默认支持,但如果连接经常断开,需要检查 Cloudflare 规则、服务端心跳和应用超时设置。
11.5 gRPC 服务
gRPC 走 HTTP/2。Traefik 接 HTTPS 后,再转发到后端 gRPC 服务时,通常需要声明服务 scheme 为 h2c。
services:
grpc-api:
image: your-grpc-api:latest
restart: unless-stopped
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.grpc-api.rule=Host(`grpc.example.com`)"
- "traefik.http.routers.grpc-api.entrypoints=websecure"
- "traefik.http.routers.grpc-api.tls.certresolver=letsencrypt"
- "traefik.http.services.grpc-api.loadbalancer.server.scheme=h2c"
- "traefik.http.services.grpc-api.loadbalancer.server.port=50051"
networks:
traefik:
external: true11.6 SSH 服务
SSH 不是 HTTP,不能用 traefik.http.* 标签,需要使用 TCP router。因为宿主机通常已经占用 22 端口,建议 Traefik 对外使用 2222,容器内部仍然转发到 22。
Traefik 主配置需要有 SSH entrypoint:
command:
- "--entrypoints.ssh.address=:2222"
ports:
- "2222:2222"Gitea SSH 示例:
services:
gitea:
image: gitea/gitea:latest
restart: unless-stopped
networks:
- traefik
volumes:
- ./gitea:/data
labels:
- "traefik.enable=true"
- "traefik.http.routers.gitea.rule=Host(`git.example.com`)"
- "traefik.http.routers.gitea.entrypoints=websecure"
- "traefik.http.routers.gitea.tls.certresolver=letsencrypt"
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
- "traefik.tcp.routers.gitea-ssh.rule=HostSNI(`*`)"
- "traefik.tcp.routers.gitea-ssh.entrypoints=ssh"
- "traefik.tcp.routers.gitea-ssh.service=gitea-ssh"
- "traefik.tcp.services.gitea-ssh.loadbalancer.server.port=22"
networks:
traefik:
external: true连接方式:
ssh -p 2222 [email protected]
git clone ssh://[email protected]:2222/用户名/仓库名.gitWarning
TCP router 的
HostSNI只有在 TLS 连接中才能按域名区分。SSH 本身没有 SNI,所以常见做法是一个 SSH entrypoint 对应一个 SSH 服务;如果有多个 SSH 服务,就分配多个外部端口,例如2222、2223。
11.7 数据库或 Redis 这类 TCP 服务
数据库、Redis、MQTT 等也属于 TCP 服务,配置方式和 SSH 类似。通常不建议直接暴露到公网,更适合通过内网、VPN、堡垒机或 Cloudflare Tunnel 访问。
如果确实要暴露,例如临时暴露 PostgreSQL:
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_PASSWORD: 强密码
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.tcp.routers.postgres.rule=HostSNI(`*`)"
- "traefik.tcp.routers.postgres.entrypoints=postgres"
- "traefik.tcp.routers.postgres.service=postgres"
- "traefik.tcp.services.postgres.loadbalancer.server.port=5432"
networks:
traefik:
external: trueTraefik 主配置需要增加:
command:
- "--entrypoints.postgres.address=:5432"
ports:
- "5432:5432"Danger
数据库直接暴露公网风险很高。至少要限制安全组来源 IP、使用强密码、开启数据库自身 TLS,并优先考虑 VPN 或内网访问。
11.8 同一个服务同时提供 HTTP 和 TCP
有些系统同时有 Web 管理后台和 TCP 协议,例如 Gitea 的 Web + SSH、RabbitMQ 的管理台 + AMQP、MinIO 的 Console + S3 API。写法就是同一个容器上同时声明 traefik.http.* 和 traefik.tcp.*。
RabbitMQ 示例:
services:
rabbitmq:
image: rabbitmq:3-management
restart: unless-stopped
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.rabbitmq.rule=Host(`rabbitmq.example.com`)"
- "traefik.http.routers.rabbitmq.entrypoints=websecure"
- "traefik.http.routers.rabbitmq.tls.certresolver=letsencrypt"
- "traefik.http.services.rabbitmq.loadbalancer.server.port=15672"
- "traefik.tcp.routers.rabbitmq-amqp.rule=HostSNI(`*`)"
- "traefik.tcp.routers.rabbitmq-amqp.entrypoints=amqp"
- "traefik.tcp.routers.rabbitmq-amqp.service=rabbitmq-amqp"
- "traefik.tcp.services.rabbitmq-amqp.loadbalancer.server.port=5672"
networks:
traefik:
external: trueTraefik 主配置需要增加:
command:
- "--entrypoints.amqp.address=:5672"
ports:
- "5672:5672"12. 常用操作
查看 Traefik 日志:
docker logs -f traefik查看当前路由:
docker exec traefik traefik version重启 Traefik:
cd /opt/traefik
docker compose restart traefik更新 Traefik:
cd /opt/traefik
docker compose pull
docker compose up -d更新前建议先备份证书文件:
cp /opt/traefik/letsencrypt/acme.json /opt/traefik/letsencrypt/acme.json.bak13. 常见问题
| 问题 | 可能原因 | 处理方式 |
|---|---|---|
| 证书一直签发失败 | 域名没有解析到服务器、80 端口未开放、DNS 未生效 | 检查 dig 域名 +short、安全组、ufw status |
| 访问域名 404 | 容器没有设置 traefik.enable=true 或 Host 规则写错 | 检查容器 labels 和 Traefik Dashboard 的 Routers |
| 访问域名 502 | Traefik 找不到业务容器端口或网络不通 | 检查 loadbalancer.server.port 和容器是否加入 traefik 网络 |
| Dashboard 无法访问 | Dashboard 域名未解析、Basic Auth 配置错误、路由 label 写错 | 查看 docker logs -f traefik |
| HTTP 没有跳转 HTTPS | 缺少 entrypoint redirection 配置 | 检查 entrypoints.web.http.redirections.entrypoint.* |
| Let’s Encrypt 频率限制 | 反复失败后频繁重试 | 排查配置后再启动;调试阶段可先使用 ACME staging server |
| Cloudflare DNS-01 失败 | API Token 权限不足或 Zone 范围不对 | 确认 token 有 Zone:Read 和 DNS:Edit,并绑定到正确域名 |
| Cloudflare 开橙云后访问异常 | SSL/TLS 模式不是 Full strict,或源站证书未签发 | 设置 Cloudflare SSL/TLS -> Full (strict),检查 Traefik 证书日志 |
.env 变量没有生效 | 文件不在 Compose 同目录,或变量名写错 | 在 /opt/traefik 执行 docker compose config 检查最终配置 |
| WebSocket 连接失败 | 后端路径错误、服务端没有处理 Upgrade、Cloudflare 规则影响 | 先绕过 Cloudflare 测试,再检查应用日志和路由规则 |
| 文件上传失败 | 后端限制、Cloudflare 上传限制、Traefik buffering 限制 | 分别检查应用配置、Cloudflare 计划限制和 maxRequestBodyBytes |
| SSH 连不上 | 没有开放 TCP entrypoint,宿主机端口冲突,TCP service 端口写错 | 检查 ports、安全组、ufw 和 traefik.tcp.services.*.port |
| 多个 SSH 服务无法按域名分流 | SSH 没有 SNI | 给不同 SSH 服务配置不同 entrypoint 和外部端口 |
14. 目录结构参考
/opt/traefik
├── .env
├── docker-compose.yml
├── whoami.yml
└── letsencrypt
└── acme.json15. 最小安全清单
- 不要开启
--api.insecure=true给公网使用。 - 不要开放
8080到公网。 - Docker socket 使用只读挂载:
/var/run/docker.sock:/var/run/docker.sock:ro。 - 默认关闭自动暴露:
--providers.docker.exposedbydefault=false。 - Dashboard 必须加 HTTPS 和 Basic Auth。
.env权限设置为600,不要公开 Cloudflare API Token。- Cloudflare API Token 只给需要的域名和最小权限。
- 使用固定版本镜像,例如
traefik:v3.6。 - 业务容器优先只加入 Traefik 网络,不直接暴露宿主机端口。
- SSH、数据库、MQTT 这类 TCP 服务要单独评估是否真的需要暴露公网。
- 能用 VPN、内网、Cloudflare Tunnel 的场景,不要直接把数据库端口暴露公网。