基于docker的caddy反向代理方案

Posted on Tue 04 March 2025 in howto

用户自行搭建的多个网络服务(self-hosted services),在使用它们时,如果不配置相应的DNS服务,则需要直接输入IP地址和端口号进行访问,非常不便。而且,如果其中还有使用了自签名证书的服务,用浏览器访问时还需要设置例外情况,很不优雅。因此,用caddy为家庭网络服务提供反向代理服务,既可以提供基于域名的访问方式,其自动申请和更新 let's encrypt 证书的功能又可以免除在浏览器中设置例外的麻烦,算是比较好的解决方案。

具体的设置方式如下:

  1. docker-compose.yaml:
services:
  caddy:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: caddy
    restart: unless-stopped
    env_file: 
      - .env
    environment:
      - CLOUDFLARE_EMAIL=${CF_EMAIL}
      - CLOUDFLARE_API_TOKEN=${CF_API_TOKEN}
      - ACME_AGREE=true
    ports:
      - 80:80
      - 443:443
    volumes:
      - caddy-config:/config
      - caddy-data:/data
      - ./Caddyfile:/etc/caddy/Caddyfile
    networks:
      - caddynet # add other containers onto this network to use dns name

volumes:
  caddy-config:
  caddy-data:

# create this first before running the docker-compose - docker network create caddy
networks:
  caddynet:
    external: true

由于我们希望给自建服务分配已注册域名之下的子域名(sub-domain),而且方便起见想为其颁发通配符证书,根据 let's encrypt 的要求,必须采用 DNS-01 challenge,为此,我们需要将域名服务商提供的 API token 以及账号对应的 email 地址写入 .env 文件。caddy 官方的 docker image 中不包含适配域名服务商进行 DNS-1 认证的模块,所以我们需要根据实际情况自行编译并加入对应的模块。

  1. Dockerfile
FROM caddy:builder AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare
FROM caddy:latest

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

假定域名服务商是 cloudflare,因此 Dockerfile 中加入了 cloudflare 相关模块。这个页面有适用于其他服务商的模块,可根据你的实际情况选用并对 Dockerfile 和 docker-compose.yaml 、.env 文件作相应的修改。

  1. Caddyfile
*.homelab.your_domain.com {
    tls {
        dns cloudflare {env.CF_API_TOKEN}
        propagation_delay 2m
        resolvers 1.1.1.1
    }

    @trueNAS host truenas.homelab.your_domain.com
    handle @trueNAS {
        reverse_proxy 192.168.6.2:80
    }

    @adguardhome host adguard.homelab.your_domain.com
    handle @adguardhome {
        reverse_proxy adguardhome:8001
    }

    @portainer host portainer.homelab.your_domain.com
    handle @portainer {
        reverse_proxy https://portainer:9443 {
            transport http {
                tls
                tls_insecure_skip_verify
            }
        }
    }
}

上面的示例中,假设子域名是 *.homelab.your_domain.com,tls 一节作用是向 let's encrypt 申请这些子域名的通配符 ssl 证书。然后,caddy 反向代理了运行在 192.168.6.2:80 的 truenas 服务、运行在名为 adguardhome 的 docker 容器中的 AdGuardHome 服务,以及一个运行在名为 portainer 的 docker 容器中的 portainer 服务。由于 portainer 提供的是 https 端口,为了避免证书的混淆,其设置中需加入 transport http 一节以忽略其自签名证书。

同时还需要注意的是,在配置 adguardhome 和 portainer 的 docker 环境时,container_name 需要与 caddyfile 中引用的一致,并且需要将它们都加入 caddy 所在的 caddynet 网络,即:

services:
  container_name: adguardhome
  networks:
    - caddynet

networks:
  caddynet:
    external: true

将上面提到的4个文件放在同一目录下。运行命令:

docker network create caddynet
docker compose up -d

祝好运。