第 11 章:Docker 部署
第 11 章:Docker 部署
11.1 Docker 环境概述
在容器化部署中,Certbot 通常以独立容器运行,与 Web 服务器容器(Nginx/Apache)通过共享卷交换证书文件。
Docker 部署架构
┌─────────────────────────────────────────────────┐
│ Docker Host │
│ │
│ ┌──────────────────┐ ┌─────────────────────┐ │
│ │ Nginx 容器 │ │ Certbot 容器 │ │
│ │ │ │ │ │
│ │ /etc/letsencrypt │◄─│ certbot renew │ │
│ │ /var/www/certbot │──│ │ │
│ │ │ │ 定期执行续期 │ │
│ └──────┬────────────┘ └─────────────────────┘ │
│ │ │
│ 共享卷: │
│ - letsencrypt-data: /etc/letsencrypt │
│ - certbot-webroot: /var/www/certbot │
│ │ │
└─────────┼────────────────────────────────────────┘
│
▼
外部请求:80/:443
Docker 方式的优势
| 优势 | 说明 |
|---|---|
| 隔离性 | Certbot 不污染宿主机环境 |
| 一致性 | 镜像版本确定,避免依赖问题 |
| 可移植 | 可在任何支持 Docker 的平台运行 |
| 易更新 | 拉取新镜像即可升级 |
| 多实例 | 不同站点可使用不同的 Certbot 配置 |
11.2 Certbot Docker 基础
基本命令
# 拉取官方镜像
docker pull certbot/certbot
# 查看版本
docker run --rm certbot/certbot --version
# 查看帮助
docker run --rm certbot/certbot --help
申请证书(Standalone 模式)
docker run --rm -it \
-p 80:80 \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/log/letsencrypt:/var/log/letsencrypt \
certbot/certbot certonly --standalone \
-d example.com \
--agree-tos \
--email [email protected]
申请证书(Webroot 模式)
docker run --rm -it \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/log/letsencrypt:/var/log/letsencrypt \
-v /var/www/certbot:/var/www/certbot \
certbot/certbot certonly --webroot \
-w /var/www/certbot \
-d example.com \
--agree-tos \
--email [email protected]
DNS 验证申请通配符证书
docker run --rm -it \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/log/letsencrypt:/var/log/letsencrypt \
-v /etc/letsencrypt/cloudflare:/etc/letsencrypt/cloudflare:ro \
certbot/certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
-d example.com \
-d "*.example.com" \
--agree-tos \
--email [email protected]
查看证书
docker run --rm \
-v /etc/letsencrypt:/etc/letsencrypt \
certbot/certbot certificates
续期
docker run --rm \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/log/letsencrypt:/var/log/letsencrypt \
-v /var/www/certbot:/var/www/certbot \
certbot/certbot renew
Docker 卷挂载说明
| 容器路径 | 宿主机路径 | 用途 |
|---|---|---|
/etc/letsencrypt | /etc/letsencrypt 或 Docker volume | 证书和配置存储 |
/var/log/letsencrypt | /var/log/letsencrypt 或 Docker volume | 日志 |
/var/www/certbot | /var/www/certbot 或 Docker volume | Webroot 验证文件 |
11.3 Docker Compose 完整部署
项目目录结构
/opt/certbot-deploy/
├── docker-compose.yml
├── nginx/
│ ├── nginx.conf
│ ├── conf.d/
│ │ └── default.conf
│ └── ssl/
│ └── options-ssl-nginx.conf
├── certbot/
│ └── cli.ini
└── www/
└── certbot/
Docker Compose 配置
# /opt/certbot-deploy/docker-compose.yml
version: "3.8"
services:
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- certbot-webroot:/var/www/certbot:ro
- certbot-certs:/etc/letsencrypt:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- certbot
restart: unless-stopped
networks:
- web
certbot:
image: certbot/certbot:latest
container_name: certbot
volumes:
- certbot-certs:/etc/letsencrypt
- certbot-webroot:/var/www/certbot
- certbot-logs:/var/log/letsencrypt
- ./certbot/cli.ini:/etc/letsencrypt/cli.ini:ro
# 默认命令(初始申请证书时使用,之后会覆盖为续期命令)
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --quiet; sleep 12h & wait $${!}; done'"
restart: unless-stopped
networks:
- web
volumes:
certbot-certs:
driver: local
driver_opts:
type: none
o: bind
device: /opt/certbot-deploy/data/certs
certbot-webroot:
driver: local
driver_opts:
type: none
o: bind
device: /opt/certbot-deploy/data/webroot
certbot-logs:
driver: local
driver_opts:
type: none
o: bind
device: /opt/certbot-deploy/data/logs
networks:
web:
driver: bridge
创建数据目录
sudo mkdir -p /opt/certbot-deploy/data/{certs,webroot,logs}
sudo mkdir -p /opt/certbot-deploy/nginx/conf.d
sudo mkdir -p /opt/certbot-deploy/nginx/ssl
sudo mkdir -p /opt/certbot-deploy/certbot
sudo mkdir -p /opt/certbot-deploy/www/certbot
Nginx 配置
# /opt/certbot-deploy/nginx/nginx.conf
user nginx;
worker_processes auto;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
# /opt/certbot-deploy/nginx/conf.d/default.conf
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/nginx/ssl/options-ssl-nginx.conf;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
root /usr/share/nginx/html;
index index.html;
}
Certbot 全局配置
# /opt/certbot-deploy/certbot/cli.ini
email = [email protected]
agree-tos = true
non-interactive = true
初始证书申请
# Step 1: 先创建一个临时的 Nginx 配置(仅 HTTP)
cat > /opt/certbot-deploy/nginx/conf.d/default.conf << 'EOF'
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
root /usr/share/nginx/html;
index index.html;
}
}
EOF
# Step 2: 启动 Nginx
cd /opt/certbot-deploy
docker compose up -d nginx
# Step 3: 申请证书
docker compose run --rm certbot certonly \
--webroot \
-w /var/www/certbot \
-d example.com \
-d www.example.com
# Step 4: 更新 Nginx 配置为完整的 HTTPS 配置
# (替换为上面的 HTTPS 配置)
# Step 5: 重启 Nginx
docker compose restart nginx
# Step 6: 启动 Certbot 自动续期
docker compose up -d certbot
11.4 Nginx + Certbot 自动续期容器
使用 Shell 脚本实现续期循环
# Dockerfile.certbot
FROM certbot/certbot:latest
RUN apk add --no-cache bash
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/bash
# entrypoint.sh
# 描述:Certbot 容器入口脚本,实现定期续期
set -e
# 获取续期间隔(默认 12 小时)
RENEW_INTERVAL=${RENEW_INTERVAL:-43200}
echo "Certbot auto-renewal started"
echo "Renewal interval: ${RENEW_INTERVAL} seconds"
# 首次运行:检查并续期
certbot renew --quiet
# 循环续期
while true; do
sleep "${RENEW_INTERVAL}" &
wait ${!}
echo "[$(date)] Running certificate renewal check..."
certbot renew --quiet \
--deploy-hook "echo 'Certificate renewed at $(date)' >> /var/log/letsencrypt/renewal.log"
done
# docker-compose.yml 片段
services:
certbot:
build:
context: .
dockerfile: Dockerfile.certbot
environment:
- RENEW_INTERVAL=43200 # 12 小时
volumes:
- certbot-certs:/etc/letsencrypt
- certbot-webroot:/var/www/certbot
- certbot-logs:/var/log/letsencrypt
restart: unless-stopped
11.5 反向代理集成
场景:Nginx 反向代理多个后端服务
version: "3.8"
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- certbot-certs:/etc/letsencrypt:ro
- certbot-webroot:/var/www/certbot:ro
restart: unless-stopped
certbot:
image: certbot/certbot:latest
volumes:
- certbot-certs:/etc/letsencrypt
- certbot-webroot:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --quiet; sleep 12h & wait $${!}; done'"
restart: unless-stopped
app-frontend:
image: node:alpine
working_dir: /app
command: npm start
volumes:
- ./frontend:/app
expose:
- "3000"
restart: unless-stopped
app-backend:
image: python:3.11-slim
working_dir: /app
command: python app.py
volumes:
- ./backend:/app
expose:
- "8000"
restart: unless-stopped
volumes:
certbot-certs:
certbot-webroot:
Nginx 反向代理配置
# ./nginx/conf.d/default.conf
upstream frontend {
server app-frontend:3000;
}
upstream backend {
server app-backend:8000;
}
server {
listen 80;
server_name example.com api.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
location / {
proxy_pass http://frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
11.6 Traefik 集成
Traefik 是一个现代化的反向代理和负载均衡器,内置 ACME 支持,可以作为 Certbot 的替代方案。
Traefik + 自动 HTTPS
# docker-compose.yml
version: "3.8"
services:
traefik:
image: traefik:v3.0
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "[email protected]"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-certs:/letsencrypt
restart: unless-stopped
webapp:
image: nginx:alpine
labels:
- "traefik.enable=true"
- "traefik.http.routers.webapp.rule=Host(`example.com`)"
- "traefik.http.routers.webapp.entrypoints=websecure"
- "traefik.http.routers.webapp.tls.certresolver=letsencrypt"
restart: unless-stopped
volumes:
traefik-certs:
注意: 如果你已经熟悉 Certbot 并希望继续使用它,可以将 Traefik 仅作为反向代理,证书管理仍交给 Certbot。
11.7 Caddy 集成
Caddy 内置自动 HTTPS,也可以配合使用。
version: "3.8"
services:
caddy:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
restart: unless-stopped
volumes:
caddy-data:
caddy-config:
# Caddyfile
example.com {
reverse_proxy app:3000
}
11.8 Docker 环境续期策略
方案一:容器内循环续期(推荐)
services:
certbot:
image: certbot/certbot:latest
volumes:
- certbot-certs:/etc/letsencrypt
- certbot-webroot:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --quiet; sleep 12h & wait $${!}; done'"
restart: unless-stopped
方案二:宿主机 cron 触发
# 宿主机 crontab
# 每天凌晨 2 点和下午 2 点执行续期
0 2,14 * * * docker exec certbot certbot renew --quiet && docker exec nginx nginx -s reload
方案三:systemd timer 触发
# /etc/systemd/system/certbot-docker.timer
[Unit]
Description=Certbot Docker renewal timer
[Timer]
OnCalendar=*-*-* 02,14:00:00
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
# /etc/systemd/system/certbot-docker.service
[Unit]
Description=Certbot Docker renewal service
[Service]
Type=oneshot
ExecStart=/usr/bin/docker exec certbot certbot renew --quiet
ExecStartPost=/usr/bin/docker exec nginx nginx -s reload
sudo systemctl daemon-reload
sudo systemctl enable certbot-docker.timer
sudo systemctl start certbot-docker.timer
11.9 Docker 安全注意事项
权限与安全性
# 只读挂载证书目录
services:
nginx:
volumes:
- certbot-certs:/etc/letsencrypt:ro # 只读挂载
网络隔离
networks:
web: # 前端网络(Nginx 对外)
driver: bridge
internal: # 内部网络(后端服务之间)
driver: bridge
internal: true
services:
nginx:
networks:
- web
- internal
certbot:
networks:
- web # 仅需要访问 Let's Encrypt 服务器
app:
networks:
- internal # 不直接暴露到外部
资源限制
services:
certbot:
image: certbot/certbot:latest
deploy:
resources:
limits:
memory: 256M
cpus: "0.5"
restart: unless-stopped
11.10 常见问题
问题 1:证书目录为空
# 确认卷正确挂载
docker compose exec nginx ls -la /etc/letsencrypt/live/
# 确认数据目录存在且有权限
ls -la /opt/certbot-deploy/data/certs/
问题 2:续期后 Nginx 未重载
# 方案 1:使用 deploy-hook
docker compose run --rm certbot renew \
--deploy-hook "echo 'renewed'"
# 方案 2:宿主机 cron 同时重载 Nginx
# 见 11.8 方案二
问题 3:Webroot 验证失败
# 确认共享卷正确配置
docker compose exec nginx cat /var/www/certbot/.well-known/acme-challenge/test
# 确认 Nginx 配置中 location 正确
docker compose exec nginx nginx -T | grep acme
11.11 最佳实践
- 使用 Docker volume: 便于数据管理和备份
- 容器内循环续期: 使用 entrypoint 脚本实现自动续期
- 网络隔离: 将 Certbot 和 Web 服务器放在同一网络
- 只读挂载: Nginx 容器中证书目录使用只读挂载
- 日志持久化: 将日志挂载到宿主机,便于排查
- 健康检查: 添加 Docker health check 监控服务状态