Caddy 是和 NginX 一样的 Web Server,支持反向代理、负载均衡等常见功能,更厉害的是 Caddy 还支持自动向 Let's Encrypt 签发 HTTPS 证书。

不过由于 Caddy2 发布不久,官方网站已不再支持 Caddy1 的在线构建和插件打包了。前几天研究了下,由于不懂 Golang,几个问题拖了不少时间,最后总算是搞定了,在此记录下来。


安装 Docker

Caddy 是使用 Golang 开发构建的,虽然安装 Go 开发环境也行,不过个人不使用 Go 进行开发作业的话,建议使用 Docker。

安装 Docker 这一步必然是必须的,不过本文不做讲解,善用搜索引擎或官方文档 Get Docker | Docker Documentation 很容易就能解决。

镜像准备

DockerHub 上搜索 Caddy,使用率最高的是 abiosoft/caddy - Docker Image | Docker Hub,不过本人尝试多次,总是会构建失败,根据 Docker-Compose Build Failure · Issue #248 · abiosoft/caddy-docker 得知,使用 --build-args plugins=xxx 添加第三方插件时可能会使用与 Caddy1 不兼容版本的其他插件。

Note: 详细的没有再看,推测是有的插件作者在 Caddy2 发布后也顺势更新了自己的插件吧,然后由于作者的镜像是使用脚本爬取插件的最新 module,就是 Golang 里 import, require 里写的类似网址的东西,所以可能出现构建 Caddy 时使用了 Caddy2 插件的情况

不过,在该 Issue 下有大佬提到他修改了 abiosoft 的镜像,并且有人使用他修改的镜像成功构建了,我决定尝试一下——从仓库开始构建镜像。

仓库地址:Yun-Mao/caddy_docker: Caddy_docker

克隆仓库

1
git clone https://github.com/Yun-Mao/caddy_docker.git

查找第三方插件(Caddy1)

Yun-Mao/caddy_docker 是基于 abiosoft/caddy 修改,不使用 --build-args 而是直接在 build.sh 中指定第三方插件的 GoPath

第三方插件根据个人需求而定,本文中添加的第三方插件是: tls.dns.cloudflare, http.forwardproxy, http.filter, http.ipfilter。还有几个是仓库原本就有:caddy-cache, caddy-minify, caddy-expires, caddy-realip。如果还需要别的插件,请善用搜索引擎+github,确认自己所需插件的 module 名。

简单来说思路就是:

  1. 通过插件的关键字找到对应的 github 仓库
  2. 在仓库的 tag 找到兼容 Caddy1 的版本
  3. 在该 tag 下找 go.mod 或者 <插件名>.go 后,确认里面的 module 名(不出意外就是第一行)

添加第三方插件

进入 caddy_docker 目录,打开 builder.sh,找到 module() 函数,根据需求修改。

我添加 tls.dns.cloudflare 没有像 Yun-Mao 那样自定义几个函数,那些好像是指定 tls 插件的 email 和 apikey 的。

一开始我也是使用 github.com/go-acme/lego 的 cloudflare 插件,修改 NewDNSProvider 等函数来构建, 后来发现使用 github.com/caddyserver/dnsproviders/cloudflare 直接构建目前也没发现什么问题,仍然可以正常使用,索性就删掉了多余的部分。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
module() {
    mkdir -p /caddy
    cd /caddy # build dir
    cat > go.mod <<EOF
    module caddy

    go 1.14

    require (
        github.com/caddyserver/caddy v1.0.5
        github.com/caddyserver/dnsproviders v0.4.0
        github.com/grpc-ecosystem/grpc-gateway v1.16.0
        github.com/lucas-clemente/quic-go v0.19.3
        github.com/captncraig/caddy-realip v0.0.0-20190710144553-6df827e22ab8
        github.com/echocat/caddy-filter v0.14.0
        github.com/epicagency/caddy-expires v1.1.1
        github.com/hacdias/caddy-minify v1.0.2
        github.com/nicolasazrak/caddy-cache v0.3.4
        github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
    )
EOF
    # main and telemetry
    cat > main.go <<EOF
    package main

    import (
        "github.com/caddyserver/caddy/caddy/caddymain"

        _ "github.com/caddyserver/dnsproviders/cloudflare"
        _ "github.com/caddyserver/forwardproxy"                 //http.forwardproxy
        _ "github.com/pyed/ipfilter"                            //http.ipfilter
        _ "github.com/echocat/caddy-filter"
        _ "github.com/nicolasazrak/caddy-cache"
        _ "github.com/hacdias/caddy-minify"
        _ "github.com/epicagency/caddy-expires"
        _ "github.com/captncraig/caddy-realip"
    )

    func main() {
        caddymain.EnableTelemetry = false
        caddymain.Run()
    }

部分 module 依赖 go1.14,所以还需要修改 Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
FROM golang:1.14-alpine as builder
RUN apk add --no-cache git gcc musl-dev
RUN mkdir /www /caddy
COPY builder.sh /usr/bin/builder.sh
ARG version="1.0.5"
RUN VERSION=${version} /bin/sh /usr/bin/builder.sh


FROM alpine:latest

ENV CADDY_VERSION=1.0.5

ENV CADDYPATH=/caddy/certs

RUN apk add --no-cache \
    ca-certificates \
    git \
    mailcap \
    openssh-client \
    tzdata


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

RUN /usr/bin/caddy -version
RUN /usr/bin/caddy -plugins

EXPOSE 80 443 2015
VOLUME /root/.caddy /www
WORKDIR /www

COPY Caddyfile /caddy/Caddyfile

CMD ["caddy","--conf", "/caddy/Caddyfile", "--log", "stdout", "--agree"]

构建镜像并启动容器

建议通过 docker-compose 构建镜像

  1. 创建 docker-compose.yaml
1
2
cd caddy_docker
touch docker-compose.yaml
  1. 编辑 docker-compose.yaml ,添加以下内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
version: '3'

services:
  caddy:
    container_name: caddy
    build:
      context: ./caddy_docker
      args:
        network: host
        # HTTPS_PROXY: 
        # NO_PROXY: localhost,127.0.0.1
    restart: always
    network_mode: "host"
    # environment:
    #   CLOUDFLARE_EMAIL: 
    #   CLOUDFLARE_API_KEY: 
    volumes:
      - ./Caddyfile:/caddy/Caddyfile
      - ./certs:/caddy/certs
      - ./.caddy:/root/.caddy
      - ./www:/www/:rw

或直接使用 cat

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cat > docker-compose.yaml <<EOF
version: '3'

services:
  caddy:
    container_name: caddy
    build:
      context: ./caddy_docker
      args:
        network: host
        # HTTPS_PROXY: 
        # NO_PROXY: localhost,127.0.0.1
    restart: always
    network_mode: "host"
    # environment:
    #   CLOUDFLARE_EMAIL: 
    #   CLOUDFLARE_API_KEY: 
    volumes:
      - ./Caddyfile:/caddy/Caddyfile
      - ./certs:/caddy/certs
      - ./.caddy:/root/.caddy
      - ./www:/www/:rw
EOF
  1. 构建
1
docker-compose build --force-rm

Tips: --force-rm 参数是为了构建结束自动删除镜像,构建失败的场景下偷懒很有用

  1. 启动 把自己的 Caddyfile 放在和 docker-compose.yaml 同目录下就可以启动了
1
docker-compose up

Note: 第一次启动建议不要加 -d,在前台可以及时看到 ssl 证书是否成功签发,服务是否已经正常启动,如果有问题直接 Ctrl+C。正常启动后要切换至后台,Ctrl+Z 就可以了。

很重要的一点,一定要把容器的 /caddy/certs 挂载到宿主机上,这个目录就是 ssl 证书的存储目录,如果不挂载到宿主机上,每次重启容器都要重新签发证书,短时间内请求过多的话自然就会被 Let’s Encrypt 拒绝访问了。

如果需要把已经构建好的 caddy 程序放到另一台同架构的机器上运行,把容器里的 /usr/bin/caddy 拷贝出来或者构建容器的时候把容器的 /install 挂载到宿主机上就行。

要记录的到这里就结束了,完整项目已上传到 github:niceRAM/caddy_docker: Caddy_docker

参考链接