本文记录 Typecho 博客从容器构建、数据库接入、反向代理到 HTTPS 上线的完整流程,并整理部署过程中出现的问题及对应处理方式。

1. 部署架构

博客采用 Typecho 1.3.0,并使用 Butterfly 风格的 Typecho 主题。

当前请求链路如下:

用户浏览器
  -> blog.sjhibqi.cn
  -> Nginx HTTPS 反向代理
  -> blog-typecho 容器
  -> site-mysql 容器
  -> personal_blog 数据库

博客使用独立域名:

https://blog.sjhibqi.cn/

MySQL 和 Docker 网络由服务器上的多个站点共用,但博客使用独立的数据库、数据库用户、容器和镜像。

2. 服务器目录

博客部署目录为:

/opt/typecho-blog

主要结构如下:

/opt/typecho-blog
├── html
│   ├── admin
│   ├── usr
│   │   ├── plugins
│   │   ├── themes
│   │   └── uploads
│   ├── var
│   └── config.inc.php
├── Dockerfile
├── docker-compose.yml
├── docker-entrypoint.sh
└── .env

html 目录挂载到容器的 /var/www/html,用于保存:

  • Typecho 程序文件
  • 站点配置
  • 主题文件
  • 插件文件
  • 上传资源

容器重建不会删除宿主机上的 html 目录。

3. 构建 Typecho 镜像

Typecho 运行环境基于 PHP 8.2 和 Apache。

镜像中启用了以下组件:

  • mysqli
  • pdo_mysql
  • Apache Rewrite 模块

当前镜像名称为:

blog-typecho:latest

当前容器名称为:

blog-typecho

容器通过 Compose 管理,并将站点目录挂载到 Apache 的网站根目录:

services:
  blog-typecho:
    build:
      context: .
      dockerfile: Dockerfile
    image: blog-typecho:latest
    container_name: blog-typecho
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - "${TYPECHO_HOST_PORT:-8091}:80"
    volumes:
      - ./html:/var/www/html
    networks:
      - site_net

networks:
  site_net:
    external: true
    name: site-net

site-net 是服务器上的共享 Docker 网络,Nginx、博客和数据库通过该网络通信。

4. 配置数据库

数据库运行在共享 MySQL 容器中:

site-mysql

博客使用独立的数据库和用户:

数据库:personal_blog
用户:blog_app

MySQL 数据保存在 Docker Volume 中:

site-mysql-data

Typecho 的 config.inc.php 使用以下连接关系:

数据库主机:site-mysql
数据库名称:personal_blog
数据库用户:blog_app

数据库密码只保存在服务器配置中,不写入文章或代码仓库。

5. 启动博客容器

在部署目录执行 Compose 启动:

cd /opt/typecho-blog
docker compose up -d

启动后可检查容器状态:

docker ps --filter name=blog-typecho
docker logs blog-typecho

容器内部由 Apache 提供 HTTP 服务,宿主机默认映射到 8091 端口。

正式访问流量不直接使用该端口,而是经过 Nginx 反向代理。

6. 配置 Nginx 反向代理

博客域名为:

blog.sjhibqi.cn

Nginx 根据 server_name 将博客请求转发到 blog-typecho 容器:

server {
    listen 443 ssl;
    server_name blog.sjhibqi.cn;

    location / {
        proxy_pass http://blog-typecho:80;

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

其中:

  • Host 用于保留原始域名。
  • X-Real-IP 用于传递客户端地址。
  • X-Forwarded-For 用于保留代理链路。
  • X-Forwarded-Proto 用于通知 Typecho 外部请求使用 HTTPS。

如果缺少 Host 或 X-Forwarded-Proto,可能出现后台跳转异常、HTTP 和 HTTPS 混用或静态资源地址错误。

7. 配置 HTTPS

HTTPS 使用 Let's Encrypt 证书。

证书文件路径为:

/etc/letsencrypt/live/sjhibqi.cn/fullchain.pem
/etc/letsencrypt/live/sjhibqi.cn/privkey.pem

证书需要包含以下域名:

sjhibqi.cn
blog.sjhibqi.cn

HTTP 入口保留 ACME 校验路径,其余请求跳转到 HTTPS:

server {
    listen 80;
    server_name blog.sjhibqi.cn;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

服务器通过定时任务执行证书续期。续期成功后重新加载 Nginx,使新证书生效:

定时任务
  -> certbot renew
  -> reload Nginx

8. 部署和调整主题

线上主题目录为:

/opt/typecho-blog/html/usr/themes/butterfly

主题部署后进行了以下调整:

  • 静态资源使用 CDN
  • 用户头像直接通过 src 加载,不使用懒加载
  • 未设置文章缩略图时不显示默认马赛克图片
  • 增加网站 favicon
  • 配置文章、标签和分类入口
  • 配置备案信息
  • 增加文章阅读量统计
  • 修复空文章状态下初始化 views 字段导致首页报错的问题

当前头像资源为:

/usr/uploads/2026/07/417480796.jpg?v=20260724

主题的部分配置保存在数据库中。修改或重新安装主题时,需要同时检查数据库中的主题配置,避免头像、首页图片和导航链接被覆盖。

9. 阅读量统计

每篇文章的阅读量保存在 Typecho 内容表的 views 字段中。

访问文章详情页时,主题执行以下逻辑:

  1. 获取当前文章 ID。
  2. 检查浏览器会话 Cookie 中是否已经记录该文章。
  3. 未记录时,将文章阅读量增加一次。
  4. 将文章 ID 写入 Cookie。
  5. 同一浏览器会话内重复刷新不重复计数。

该方案按浏览器会话去重,不按账号或 IP 去重。无痕窗口、新设备、Cookie 被清除或不保留 Cookie 的爬虫可能产生新的计数。

侧栏的“本站总访问量”为已发布文章阅读量之和,并使用缓存减少数据库查询。

10. 备份与回滚

修改以下内容前需要创建备份:

  • config.inc.php
  • .env
  • docker-compose.yml
  • Butterfly 主题文件
  • usr/uploads
  • personal_blog 数据库

现有备份目录为:

/opt/backups/20260724-naming

数据库数据实际保存在 site-mysql-data Volume 中。数据库 dump 用于逻辑恢复,Volume 用于容器级持久化。

主题文件修改前应单独复制原文件。回滚时恢复原文件即可,不需要重建整个站点。

11. 部署过程中出现的问题

现象原因处理方式
HTTPS 配置完成后外网无法访问云服务器安全组未开放 TCP 443增加 443 入方向规则
页面跳转到 HTTPTypecho 站点地址或代理协议头配置错误将站点地址设为 HTTPS,并传递 X-Forwarded-Proto
静态资源地址异常反向代理未传递原始 Host配置 proxy_set_header Host $host
修改主题配置后首页返回 500主题配置内容或格式不符合预期恢复主题配置并检查错误日志
删除全部文章后首页返回 500主题通过第一篇文章判断 views 字段,空表时无法取得文章改为直接检查数据库表结构
首页出现默认马赛克缩略图主题在文章无图片时使用默认图调整缩略图逻辑,无真实图片时不输出缩略图
用户头像加载异常头像使用懒加载属性,但相关脚本未正确处理将头像调整为标准 src 加载
网站图标为空主题 favicon 配置未设置增加默认 64×64 PNG favicon
更改容器名称后服务无法通信Compose、Nginx 和 Docker 网络中的服务名未同步统一使用 blog-typecho 和 site-net

12. 上线验证

部署完成后需要检查以下项目:

curl -I http://blog.sjhibqi.cn/
curl -I https://blog.sjhibqi.cn/
curl -I https://blog.sjhibqi.cn/admin/

预期结果:

  • HTTP 自动跳转到 HTTPS。
  • 博客首页返回 200。
  • 后台入口返回登录页或跳转到登录页。
  • CSS、JavaScript、图片和 favicon 返回 200。
  • 发布文章后首页和文章详情页可以正常访问。
  • 上传文件写入 html/usr/uploads。
  • 容器重建后文章、配置和上传资源仍然存在。

13. 日常维护

后续维护主要包括:

  • 通过 Typecho 后台发布和管理文章
  • 定期备份 personal_blog 数据库
  • 定期备份 usr/uploads
  • 检查 HTTPS 证书续期状态
  • 检查 blog-typecho 和 site-mysql 容器状态
  • 修改主题前备份目标文件
  • 更新 Typecho 或主题后检查现有定制逻辑
  • 定期验证首页、后台、静态资源和数据库连接

当前线上正式命名为:

容器:blog-typecho
镜像:blog-typecho:latest
数据库:personal_blog
数据库用户:blog_app
Docker 网络:site-net