NoctisNode
← 指南自托管2026-10-239 分钟

Caddy Docker 反向代理:带自动 HTTPS 的 Compose 配置

将 Caddy 作为 Docker 反向代理运行:一个 compose 栈、一个共享网络、按服务名 reverse_proxy 并自动 HTTPS,以及何时适合使用 caddy-docker-proxy 标签。

简答:让 Caddy 运行在独立容器中并发布 80 和 443 端口,把它和你的应用容器接入同一个共享 Docker 网络,然后让 `reverse_proxy` 指向各个服务名和内部端口(例如 `reverse_proxy app:3000`)。Caddy 通过 Docker 内置 DNS 解析服务名,并自动为每个站点地址获取 TLS 证书。

本指南先介绍纯 Caddyfile 方案,再介绍基于标签的 caddy-docker-proxy 方案,并说明在单台 VPS 上各自适用的场景。如果你直接在宿主机而非 Docker 中运行 Caddy,请参阅基础的 Caddy 反向代理指南。

各组件如何协同工作

只有 Caddy 容器向外部发布端口。每个后端容器都加入一个用户自定义的 bridge 网络(这里叫 `web`),并且只在该网络上暴露端口。用户自定义网络提供的 Docker 内置 DNS 让 Caddy 能通过服务名或容器名访问 `app:3000`,这样你就无需硬编码每次重建都会变化的容器 IP。

不发布后端端口比看上去更重要:Docker 会写入自己的 iptables 规则,因此即使 ufw 显示已拦截,用 `ports:` 发布的端口仍可能从公网访问。凡是应该位于代理之后的服务,请使用 `expose:` 或完全不做端口映射。

组件作用是否在宿主机发布?
caddy 容器TLS 终止、按主机名路由、HTTP 到 HTTPS 重定向是:80/tcp、443/tcp、443/udp
web 网络共享 bridge 网络,内置服务名 DNS否
应用容器你的服务(Web 应用、API、仪表盘)否,仅能通过 Caddy 访问
caddy_data 卷证书、私钥、ACME 账户否,但必须持久化

分步操作:纯 Caddyfile + docker compose

  1. 01.在启动 Caddy 之前,把每个主机名的 A 记录(如使用 IPv6 还需 AAAA 记录)指向 VPS,否则证书签发会失败。
  2. 02.创建一次共享网络:`docker network create web`。
  3. 03.按下文所示创建 `/opt/proxy/compose.yaml` 和 `/opt/proxy/Caddyfile`,然后在 `/opt/proxy` 中运行 `docker compose up -d`。
  4. 04.将每个应用栈接入外部 `web` 网络,并用 `docker compose up -d` 启动。
  5. 05.在 `/opt/proxy` 中用 `docker compose logs -f caddy` 观察首张证书的签发过程。
  6. 06.编辑 Caddyfile 后无停机重载:`docker compose exec -w /etc/caddy caddy caddy reload`。
# /opt/proxy/compose.yaml
services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"   # HTTP/3
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data      # certificates and ACME account keys
      - caddy_config:/config
    networks:
      - web

volumes:
  caddy_data:
  caddy_config:

networks:
  web:
    external: true

接入应用并编写 Caddyfile

每个应用栈都声明同一个外部网络。服务名就是 Caddy 要连接的主机名。`caddy_data` 卷绝不能丢失:删除它会迫使 Caddy 重新申请所有证书,可能触发 ACME 速率限制。

# /opt/app/compose.yaml
services:
  app:
    image: ghcr.io/example/app:latest
    restart: unless-stopped
    expose:
      - "3000"        # reachable on the network, NOT published on the host
    networks:
      - web

networks:
  web:
    external: true

# /opt/proxy/Caddyfile
{
	email [email protected]
}

app.example.com {
	reverse_proxy app:3000
}

grafana.example.com {
	reverse_proxy grafana:3000
}

api.example.com {
	reverse_proxy api-1:8080 api-2:8080 {
		lb_policy round_robin
		health_uri /healthz
		health_interval 10s
	}
}

Caddy 后端:上游、负载均衡与健康检查

在 Caddy 的术语中,后端就是 `reverse_proxy` 的上游(upstream)。你可以在一条指令中列出多个上游,选择 `round_robin`、`least_conn` 或 `ip_hash` 等 `lb_policy`,并用 `health_uri` 添加主动健康检查,让故障容器退出轮询。当应用没有健康检查端点时,被动检查(`fail_duration`、`max_fails`)也很有用。

Caddy 默认会在代理请求上设置 `X-Forwarded-For`、`X-Forwarded-Proto` 和 `X-Forwarded-Host`。请将应用配置为信任代理所在的网段,这样它才能看到真实客户端 IP 并生成 `https://` 链接。WebSocket 和 gRPC 无需额外设置即可通过 `reverse_proxy`;对于明文 gRPC,使用 `reverse_proxy h2c://service:50051`。

用标签代替 Caddyfile:caddy-docker-proxy

搜索“Caddy proxy manager”的人,通常想要的是 Nginx Proxy Manager 那样的体验:添加一个容器,无需编辑中心配置文件就能获得主机名和证书。Caddy 没有官方图形界面,但社区项目 caddy-docker-proxy(github.com/lucaslorentz/caddy-docker-proxy)会根据容器标签生成 Caddyfile,并在容器启动或停止时重载 Caddy。

它需要读取 Docker socket,这实际上等同于宿主机的 root 权限。请以只读方式挂载,保持代理镜像更新;如果你只运行少量服务,优先选择纯 Caddyfile。

services:
  caddy:
    image: lucaslorentz/caddy-docker-proxy:ci-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    environment:
      - CADDY_INGRESS_NETWORKS=web
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - caddy_data:/data
    networks:
      - web

  whoami:
    image: traefik/whoami
    networks:
      - web
    labels:
      caddy: whoami.example.com
      caddy.reverse_proxy: "{{upstreams 80}}"

volumes:
  caddy_data:

networks:
  web:
    external: true

纯 Caddyfile 与 caddy-docker-proxy 对比

纯 Caddyfilecaddy-docker-proxy 标签
配置存放位置一个纳入 git 版本管理的文件分散在各个 compose 文件的标签中
Docker socket 访问不需要需要(安全敏感)
新增服务编辑 Caddyfile,运行 `caddy reload`添加标签,启动容器
镜像官方 `caddy:2`基于 Caddy 的社区镜像
调试直接阅读文件在日志中查看生成的配置
最适合小型、稳定的 VPS 部署大量短生命周期或频繁变更的服务

在 VPS 上运行

在 NoctisNode KVM VPS 上,你拥有完整 root 权限和独立 IPv4,因此由一个 Caddy 容器发布 80 和 443、隐藏其他所有服务非常简单。Wisp(1 vCPU、1 GB 内存)足以运行 Caddy 加几个轻量容器;更重的技术栈适合 Pulse 或 Umbra。证书签发细节和故障模式见 Caddy 自动 HTTPS 的工作原理,原始 TCP 或 SSH 转发见 Caddy TCP 与 SSH 代理。用 SSH 加固指南锁定宿主机 SSH。

常见问题

Q如何让 Caddy 反向代理到 Docker 容器?

A把 Caddy 和该容器放在同一个用户自定义 Docker 网络中,并在 Caddyfile 中使用 `reverse_proxy service-name:port`。不要使用 `localhost`,因为在 Caddy 容器内它指的是 Caddy 自己。

Q为什么 Caddy 对我的容器返回 502 Bad Gateway?

A通常是容器不在共享网络中、端口错误(应使用容器的内部端口,而非宿主机发布端口),或应用在容器内只监听 127.0.0.1。让应用绑定 0.0.0.0,并检查 `docker network inspect web`。

Qcaddy-docker-proxy 安全吗?

A它运行良好,但需要 Docker socket,这相当于授予宿主机的 root 级控制权。以只读方式挂载,保持镜像更新,并避免暴露 Caddy 管理端点。

QDocker 中的 Caddy 把证书存在哪里?

A在官方镜像内的 `/data`,因此应在该处挂载一个命名卷。丢失它意味着要重新签发所有证书,并可能触发 ACME 速率限制。

Q有带 Web 界面的 Caddy proxy manager 吗?

ACaddy 本身没有官方 Web 界面;它通过 Caddyfile、JSON 或管理 API 配置。caddy-docker-proxy 等基于标签的工具提供了类似“添加容器即可使用”的工作流。

Caddy Docker 反向代理:带自动 HTTPS 的 Compose 配置 · NoctisNode