目的是构建一个基于 Typecho 博客系统的完整部署方案,包含博客应用、数据库和 Nginx 反向代理(带自动 SSL 证书生成)。

首先展示一下我的docker-compose.yml文件

version: '3'

services:
  # Typecho 服务
  typecho:
    image: joyqi/typecho:nightly-php8.2-apache
    container_name: typecho
    restart: unless-stopped
    volumes:
      - ./typecho:/var/www/html
    environment:
      - TYPECHO_DB_HOST=db
      - TYPECHO_DB_NAME=typecho
      - TYPECHO_DB_USER=typecho
      - TYPECHO_DB_PASSWORD=your_password  # 替换为你的数据库密码
    depends_on:
      - db
    networks:
      - app-network

  # MySQL 服务
  db:
    image: mysql:5.7  # 兼容性更好的 MySQL 版本
    container_name: typecho_db
    restart: unless-stopped
    volumes:
      - ./mysql:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=your_root_password  # 替换为 root 密码
      - MYSQL_DATABASE=typecho
      - MYSQL_USER=typecho
      - MYSQL_PASSWORD=your_password  # 与 TYPECHO_DB_PASSWORD 一致
    networks:
      - app-network

  # Nginx + Certbot
  nginx:
    image: jonasal/nginx-certbot:latest
    container_name: nginx_certbot
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/logs:/var/log/nginx
      - ./certbot/certs:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    environment:
      - CERTBOT_EMAIL=your_email@example.com  # 替换为你的邮箱
      - CERTBOT_DOMAINS=example.com,www.example.com  # 你的域名
      - CERTBOT_STAGING=false  # 生产环境使用 false,测试时可以设为 true
    depends_on:
      - typecho
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

踩坑就是从这个文件开始的。

1、确保你的系统已安装 Docker 和 Docker Compose:

# 检查 Docker 是否安装
docker --version

# 检查 Docker Compose 是否安装
docker-compose --version
如果没有安装,请按照 Docker 官方文档安装 Docker 和 Docker Compose。

2、创建项目目录结构

mkdir -p my-blog/{typecho,mysql,nginx/{conf.d,logs},certbot/{certs,www}}
cd my-blog

3、创建 Nginx 配置文件

touch nginx/conf.d/default.conf

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;
    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;

    location / {
        proxy_pass http://typecho;
        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;
    }
}

按照我的想法,现在只要运行

docker-compose up -d
docker-compose ps

一旦所有服务都启动并正常运行,通过浏览器访问你的域名(https://example.com),你将看到 Typecho 的安装向导。这样就算是搞成了。

博客维护也很简单

# 查看日志
docker-compose logs -f

# 停止服务
docker-compose down

# 重启服务
docker-compose restart

# 更新服务(如有新版本)
docker-compose pull
docker-compose up -d

# 备份数据库
docker exec typecho_db mysqldump -u typecho -pzmind typecho > backup_$(date +%Y%m%d).sql

# 备份网站文件
cp -r typecho/ backup_typecho_$(date +%Y%m%d)/

一切都很完美但是我还是想当然了。
目前这个方案会因为获取ssl证书失败,而导致网站服务都用不了。
大概的原因是容器启动顺序问题可能会导致验证失败,尤其是当 Nginx 配置期望证书已经存在,而证书又需要通过 Nginx 的 HTTP 验证来获取时,这种循环依赖可能会导致问题。
问题的关键就是证书一开始不存在。

所以我们需要通过分阶段部署来解决这个问题:先部署一个专门用于获取证书的简单配置,获取证书后再部署完整的博客系统。
分阶段部署解决方案
步骤 1: 创建一个简单的证书获取配置
创建一个名为 docker-compose.certbot.yml 的文件:

cat > docker-compose.certbot.yml << 'EOF'
version: '3'

services:
  certbot-nginx:
    image: nginx:alpine
    container_name: certbot_nginx
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./nginx/simple-conf:/etc/nginx/conf.d
      - ./certbot/www:/var/www/html
    networks:
      - cert-network

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - ./certbot/certs:/etc/letsencrypt
      - ./certbot/www:/var/www/html
    depends_on:
      - certbot-nginx
    command: certonly --webroot --webroot-path=/var/www/html -d example.com -d www.example.com --email your_email@example.com --agree-tos --no-eff-email
    networks:
      - cert-network

networks:
  cert-network:
    driver: bridge
EOF

步骤 2: 创建简单的 Nginx 配置

# 创建简单配置目录
mkdir -p ./nginx/simple-conf

# 创建简单配置文件
cat > ./nginx/simple-conf/default.conf << 'EOF'
server {
    listen 80;
    server_name example.com www.example.com;
    
    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }
    
    location / {
        return 200 "Certbot validation server!";
    }
}
EOF

步骤 3: 运行证书获取配置

# 停止现有服务(如果有)
docker-compose down

# 清理现有证书(如果需要重新获取)
sudo rm -rf ./certbot/certs/*
sudo mkdir -p ./certbot/certs
sudo chmod -R 755 ./certbot/certs

# 清理验证目录
sudo rm -rf ./certbot/www/*
sudo mkdir -p ./certbot/www
sudo chmod -R 755 ./certbot/www

# 运行证书获取配置
docker-compose -f docker-compose.certbot.yml up -d certbot-nginx

# 检查 Nginx 是否正确启动
docker ps

# 测试 ACME 验证路径
echo "test file" > ./certbot/www/.well-known/acme-challenge/test.txt
curl http://example.com/.well-known/acme-challenge/test.txt

如果测试成功,就可以执行证书获取了:

# 运行 certbot 容器获取证书
docker-compose -f docker-compose.certbot.yml up certbot

# 检查是否成功获取证书
sudo ls -la ./certbot/certs/live/example.com/

步骤 4: 在获取证书后部署完整的博客系统
一旦证书成功获取,就可以停止证书获取的服务,启动完整的博客系统:

# 停止证书获取服务
docker-compose -f docker-compose.certbot.yml down

# 修改正式配置中的 Nginx 设置,确保使用正确的证书路径
# 编辑 nginx/conf.d/default.conf 确保 SSL 配置正确

# 启动正式博客系统
docker-compose up -d

步骤 5: 更新原始 Nginx 配置
确保你的 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;
    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;
    
    # 其他 SSL 配置...
    
    location / {
        proxy_pass http://typecho;
        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;
    }
}

证书自动续期脚本
为了简化将来的证书续期过程,可以创建一个自动续期脚本:

cat > renew-cert.sh << 'EOF'
#!/bin/bash
docker run --rm \
  -v $(pwd)/certbot/certs:/etc/letsencrypt \
  -v $(pwd)/certbot/www:/var/www/html \
  certbot/certbot renew

# 重新加载 Nginx 配置
docker exec nginx_certbot nginx -s reload
EOF

chmod +x renew-cert.sh

这种分阶段的方法可以有效解决容器启动顺序和证书获取的循环依赖问题。先获取证书,然后再部署完整的博客系统,确保 HTTPS 配置能够正确加载和使用已获取的证书。
一旦初始证书获取成功,后续的自动续期会通过 jonasal/nginx-certbot 镜像的内置机制处理,无需再手动干预。