很多使用Caddy运行容器化HTTPS服务的开发者,常会碰到一个棘手的问题:容器重启后,原本正常的HTTPS服务直接失效,浏览器显示“不安全”提示,用户不敢访问。追根溯源,十有八九是证书数据卷的挂载策略出了问题。今天就用大白话把这个问题的根因、解决办法、自动续期的保障方案一次性说清楚,就算刚接触容器的新手也能上手。

一、容器中Caddy证书丢失的真实场景

1.1 常见的错误操作

很多新手第一次跑Caddy容器,图省事直接用官方镜像启动,只做了端口映射,却把Caddy的核心文件(证书、配置)都存在了容器内部。比如这个错误的启动命令,完全没处理证书的持久化:

# 错误示例:仅映射端口,证书全存在容器内,重启必丢
docker run -d -p 80:80 -p 443:443 caddy:latest

这就好比你把重要的工作论文存在学校机房的公共电脑桌面,电脑一重启,文件直接消失,根本找不回。

1.2 丢失后的连锁反应

证书丢失带来的麻烦不止是网站显不安全:Caddy本身带自动续期功能,但它需要旧证书的记录来申请新证书,要是容器重启后证书没了,不仅续期失败,还会触发Let’s Encrypt的频率限制——同一域名短时间内多次申请证书,会被限制,搞不好要等好几天才能重新获取证书,直接导致服务瘫痪。

二、证书持久化的最佳实践

2.1 选对卷类型:命名卷比绑定挂载更靠谱

Docker管理数据的卷主要有两种:命名卷和绑定挂载。用生活化的类比理解:命名卷是Docker专门给你准备的“专属U盘”,不管你怎么插拔(容器启停/删除),里面的数据都不会丢;绑定挂载是你自己插的U盘(宿主机目录),虽然也能用,但容易因为宿主机权限问题出状况,还不容易追踪数据位置。存Caddy这种重要证书,优先选命名卷。

2.2 完整配置示例:用Docker Compose实现持久化

这里用Docker Compose来配置,比纯docker run更清晰,适合管理多个服务。先给错误的配置做对比,再上正确的示例: 错误的docker-compose.yml(未做卷挂载):

version: '3.8'
services:
  caddy:
    image: caddy:latest
    ports:
      - "80:80"
      - "443:443"

正确的docker-compose.yml(挂载命名卷,证书和配置都不会丢):

version: '3.8'
services:
  caddy:
    image: caddy:latest
    ports:
      - "80:80"
      - "443:443"
    # 挂载两个命名卷,分别存Caddy配置和SSL证书
    volumes:
      - caddy_config:/etc/caddy/config
      - caddy_certs:/etc/caddy/certs
    # 指定Caddy的启动命令,用外部的Caddyfile(不用进容器改配置)
    command: caddy run --config /etc/caddy/Caddyfile
volumes:
  # 定义命名卷,Docker会自动在宿主机管理这些数据,不会随容器消失
  caddy_config:
  caddy_certs:

这里的关键点是:Caddy默认把证书存在/etc/caddy/certs,配置存在/etc/caddy/config,把这两个目录映射到Docker命名卷,不管容器怎么重启、删除,证书都安稳存在。

2.3 验证挂载是否生效

启动服务后,用命令docker volume ls就能看到Docker创建的两个命名卷caddy_configcaddy_certs,说明挂载成功。随便重启几次容器,再查看证书目录,数据依然存在,完全不会丢。

三、自动续期的保障方案

3.1 Caddy原生的自动续期能力

Caddy天生带Let’s Encrypt证书的自动申请和续期功能,这也是它比Nginx省心的地方——不用你写定时脚本续期,它会每隔一段时间自动检查证书有效期,快过期了就自动换新。但这个功能生效的前提是:证书必须是持久化的,不然续期时找不到旧证书,就会重复申请触发限流。

3.2 优化配置:让续期更稳定

把Caddy的配置文件也映射到宿主机,改配置不用进容器,还能单独管理。先写一个简单的Caddyfile,把你的域名和邮箱填进去:

# 替换成你的域名
example.com {
  # 替换成你的邮箱,方便收到续期失败的通知
  tls admin@example.com
  # 简单的静态文件服务,实际用可以改反向代理等配置
  file_server
}

再把这个文件加到docker-compose的卷里,最终的docker-compose.yml完整版:

version: '3.8'
services:
  caddy:
    image: caddy:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile # 映射宿主机的Caddyfile,改配置直接改宿主机文件
      - caddy_config:/etc/caddy/config
      - caddy_certs:/etc/caddy/certs
volumes:
  caddy_config:
  caddy_certs:

改好Caddyfile后,重启容器就会生效,而且后续想加其他配置,直接改宿主机的Caddyfile就行,不用再折腾容器。

3.4 避免续期失败的坑

三个关键点要记牢:第一,你的域名必须是公网可访问的,不能是localhost这种本地域名;第二,宿主机的80和443端口必须开放,不能被防火墙或云服务商的安全组挡住,不然Caddy申请不到证书;第三,千万不要手动删除命名卷,删了就等于把证书和配置一起删了。

四、应用场景分析与注意事项

4.1 适用场景

这个方案适合大部分容器化HTTPS场景:个人开发者的小网站、团队内部服务的HTTPS部署、微服务的反向代理(用Caddy做入口),只要是需要稳定HTTPS的容器服务,都适用。

4.2 技术优缺点

优点:配置简单,比Nginx的SSL步骤少很多;Caddy自动续期,不用额外写定时任务;命名卷稳定,数据不会随容器消失; 缺点:命名卷在宿主机上是隐藏的,要备份的话得专门操作,不过可以手动备份宿主机的/var/lib/docker/volumes/下对应卷的文件夹;如果用绑定挂载,容易出现权限问题,Caddy读不了证书文件。

4.3 注意事项

第一,绝对不要把证书存在容器内部,哪怕是临时容器也不行,容器删除时内部数据全部清空;第二,如果你用国内域名,可以申请国内的SSL证书,改Caddyfile的tls配置就行,不过大部分情况Let’s Encrypt完全够用;第三,别频繁删除命名卷,除非你确定里面的配置和证书没用了。

五、总结

容器化Caddy的证书丢失问题,核心就是没做数据持久化。只要用Docker命名卷挂载Caddy的配置和证书目录,结合Caddy原生的自动续期功能,就能彻底解决重启后证书丢失的问题。整个过程没有复杂的配置,几分钟就能搞定,不管是刚接触容器的新手,还是有经验的开发者,都能快速上手,再也不用怕网站突然变“不安全”了。