部署轻量化博客 CMS - Typecho

一、前言

1.1 适用环境

本教程基于轻量应用服务器环境编写,适用于 2 核 2GB 内存 40GB 磁盘及以上的服务器。操作系统以 Ubuntu 22.04 / 24.04 为例,大部分命令在 CentOS / RedHat 上也可通用(仅包管理命令不同:Ubuntu / Debian 用 apt,CentOS / RedHat 用 yum / dnf)。

1.2 为什么用 Docker Compose

相比传统在服务器内部直接安装 LNMP(Linux + Nginx + MySQL + PHP),Docker Compose 有以下优势:

  • 环境隔离:所有服务运行在独立容器中,互不干扰,不污染宿主机系统环境。
  • 一键部署:一条 docker compose up -d 即可完成整个栈的启动,迁移或重建成本极低。
  • 数据持久化:通过 bind mount 将容器数据挂载到宿主机目录,容器删除重建后数据依然存在。
  • 版本可控:每个服务使用指定镜像版本,升级回滚简单透明,不会出现依赖冲突。
  • 资源可控:可为每个容器单独设置 CPU 和内存上限,防止某个服务吃满资源导致整机崩溃。

1.3 最终架构概览

用户浏览器 → Nginx(端口 80) → PHP-FPM(Typecho 应用) → MariaDB(数据库)

三个容器通过自定义网络 typecho-net 互通,数据通过 bind mount 持久化到宿主机 /opt/cms-server/typecho/ 目录下。


二、准备工作

2.1 购买服务器、记录公网 IP 和账号密码

在云服务商(如阿里云、腾讯云等)购买云服务器实例后,请务必记录好:

  • 公网 IP 地址
  • 登录用户名(通常为 rootubuntu
  • 登录密码

这些信息在后续通过 SFTP / SSH 连接服务器时会用到。

2.2 SFTP 工具连接(MobaXterm)

本教程使用 MobaXterm 个人版(免费)。相比云服务商自带的 Web 终端(如阿里云 Workbench),第三方 SFTP 工具在文件上传、命令执行上更方便。

连接步骤:

  1. 打开 MobaXterm,左上角点击 Session → SSH
  2. 在 Remote host 中输入公网 IP
  3. 点击 OK,提示 Login 时输入用户名,提示 Password 时输入密码
  4. 连接成功后进入 Command 模式(命令终端)

2.3 确认系统版本

cat /etc/os-release

根据输出确认系统类型:

  • CentOS / RedHat:包管理用 yumdnf
  • Ubuntu / Debian:包管理用 apt

2.4 关于 sudo 权限

登录终端后默认是普通用户。执行系统层面的命令时需要在前面加 sudo,输入后会提示输入密码以获取临时管理员权限。涉及系统文件编辑时,如果没有 sudo,通常会提示"该文件只能读,不可写入"。

提示:虽然可以执行 sudo -i 直接进入超级管理员模式,但不建议初学者这样做。养成在系统级命令前加 sudo 的习惯更安全。

三、安装 Docker 与 Compose

3.1 安装 Docker

在命令行执行(国内加速脚本):

bash <(wget -qO- https://get.xuanyuan.cloud/docker.sh)

备用地址:

bash <(wget -qO- https://get.xuanyuan.me/docker.sh)
安全提示:上述为国内第三方一键安装脚本,执行前建议查看脚本内容。如需官方安装方式,请参考:https://docs.docker.com/engine/install/ubuntu/

执行后提示"请选择操作模式",输入 1 进行安装。等待约 5~10 分钟。安装完成后选择 1(免费版)继续。

3.2 验证安装

docker -v && docker compose version

显示版本号即安装成功。

3.3 配置镜像加速器

免费版 Docker 源有时不太稳定,建议添加多个镜像源:

sudo tee /etc/docker/daemon.json <<EOF
{
  "registry-mirrors": [
    "https://docker.xuanyuan.me",
    "https://docker.1ms.run",
    "https://docker.m.daocloud.io"
  ]
}
EOF

重启 Docker 使配置生效:

sudo systemctl daemon-reload
sudo systemctl restart docker

验证:

sudo docker pull hello-world

拉取成功即配置正确。若提示节点繁忙,可更换其他免费源,或考虑购买付费源。


四、部署 Typecho

4.1 创建目录结构

建议部署在 /opt 目录下——Linux 惯例中专门放第三方 / 额外安装软件的目录,权限清晰、备份迁移干净、不会被系统更新误伤。

sudo mkdir -p /opt/cms-server/typecho && cd /opt/cms-server/typecho
pwd   # 确认当前目录为 /opt/cms-server/typecho

目录结构如下:

/opt/cms-server/typecho/
├── docker-compose.yml
├── .env                      # 密码等敏感配置
├── nginx/
│   ├── conf.d/
│   │   └── typecho.conf
│   └── logs/
├── typecho-data/             # Typecho 程序文件(持久化)
└── mariadb-data/             # 数据库数据(持久化)

创建必要文件夹:

mkdir -p nginx/conf.d nginx/logs typecho-data mariadb-data

4.2 编写 .env 文件

.env 管理密码等敏感配置,避免在 compose 文件中硬编码。

vim .env

写入以下内容(请替换为自己的强密码):

# /opt/cms-server/typecho/.env
MYSQL_ROOT_PASSWORD=YourStrongRootPwd123
MYSQL_DATABASE=typecho
MYSQL_USER=typecho
MYSQL_PASSWORD=YourStrongTypechoPwd456
安全提示:示例密码请替换为自己的强密码,建议包含大小写字母、数字和特殊字符,长度不少于 12 位。

4.3 编写 docker-compose.yml

vim docker-compose.yml

写入以下完整内容:

services:
  # 1. MariaDB
  db:
    image: mariadb:10.6
    container_name: typecho-db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - ./mariadb-data:/var/lib/mysql
    networks:
      - typecho-net
    # 2c2g 内存有限,给数据库限个上限
    deploy:
      resources:
        limits:
          memory: 768M
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  # 2. Typecho (PHP-FPM)
  app:
    image: joyqi/typecho:nightly-php7.4-fpm
    container_name: typecho-app
    restart: always
    volumes:
      # 挂载 Typecho 程序目录,容器重建数据不丢
      - ./typecho-data:/app
    networks:
      - typecho-net
    depends_on:
      db:
        condition: service_healthy
    deploy:
      resources:
        limits:
          memory: 512M

  # 3. Nginx
  web:
    image: nginx:alpine
    container_name: typecho-web
    restart: always
    ports:
      - "80:80"
    volumes:
      # Nginx 站点配置(只读)
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      # Nginx 日志
      - ./nginx/logs:/var/log/nginx
      # 共享 Typecho 程序目录给 Nginx(关键:让 Nginx 能读到 php 文件)
      - ./typecho-data:/app:ro
    networks:
      - typecho-net
    depends_on:
      - app
    deploy:
      resources:
        limits:
          memory: 256M

networks:
  typecho-net:
    driver: bridge

配置说明

  • 使用 depends_on + healthcheck 确保数据库就绪后 Typecho 才启动(避免首次安装报连不上库)
  • 使用 bind mount 共享 typecho-data 给 Nginx,替代已过时的 volumes_from
  • .env 存密码,compose 文件中用 ${VAR} 引用,避免密码硬编码
  • 给三个服务限制内存,防止 2C2G 服务器被吃满触发 OOM
  • 挂载 typecho-datamariadb-data 到宿主机,容器删除 / 重建后数据不丢失

4.4 编写 Nginx 配置

cd /opt/cms-server/typecho/nginx/conf.d
vim typecho.conf

写入以下完整配置:

server {
    listen 80;
    server_name _;   # 没有域名就写 _,有域名替换成你的域名

    root /app;
    index index.php index.html index.htm;

    access_log /var/log/nginx/typecho_access.log;
    error_log  /var/log/nginx/typecho_error.log;
    client_max_body_size 20m;   # 允许上传稍大的附件

    # Typecho 伪静态
    location / {
        if (!-e $request_filename) {
            rewrite ^(.*)$ /index.php$1 last;
        }
    }

    # PHP-FPM 处理
    location ~ \.php$ {
        fastcgi_pass app:9000;   # 用服务名 app 连接 PHP-FPM
        fastcgi_index index.php;
        fastcgi_split_path_info ^(.+\.php)(/?.+)$;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        include fastcgi_params;
    }

    # 禁止访问敏感文件
    location ~ /\.(?!well-known).* {
        deny all;
    }
}

4.5 启动服务

cd /opt/cms-server/typecho
docker compose up -d

4.6 验证部署

# 查看容器状态,确认三个容器都处于 running / healthy
docker compose ps

# 查看日志排查问题
docker compose logs -f app
docker compose logs -f web
docker compose logs -f db

如果 Typecho 页面提示无法写入 config.inc.php,调整目录权限:

# PHP-FPM 容器里通常是 www-data (UID 33),把 typecho-data 属主改成它
chown -R 33:33 typecho-data
提示joyqi/typecho 镜像里 PHP-FPM 运行用户一般是 www-data,对应宿主机 UID 33。如果你用其他 Typecho 镜像,先执行 docker compose exec app id 确认实际 UID 再修改。

五、初始化 Typecho

5.1 放行安全组端口

在云服务商控制台的安全组规则中放行 80 端口(HTTP)443 端口(HTTPS,后续配置证书需要)

5.2 访问安装向导

浏览器访问:

http://你的服务器IP

进入 Typecho 安装向导页面。

5.3 填写数据库信息

字段填写内容对应 .env 变量
数据库地址db(服务名,不是 localhost)
数据库端口3306
数据库用户名typecho${MYSQL_USER}
数据库密码你自己设置的密码${MYSQL_PASSWORD}
数据库名typecho${MYSQL_DATABASE}
表前缀typecho_(默认即可)
重要:数据库地址必须填 db(Docker Compose 中定义的服务名),不能填 localhost127.0.0.1,否则容器之间无法通信。

5.4 设置后台管理员账号

填写站点名称、管理员用户名、密码和邮箱,完成安装。安装完成后即可使用管理员账号登录 Typecho 后台。

5.5 验证伪静态

安装完成后,访问任意文章页面(非首页),确认伪静态是否生效。如果提示 404,请检查 Nginx 配置中的伪静态规则是否正确写入,以及 Nginx 容器是否正常重启。


六、进阶配置(可选)

6.1 绑定域名

server_name_ 替换为你的域名:

server_name your-domain.com www.your-domain.com;

然后在云服务商的 DNS 解析中,将域名 A 记录指向服务器公网 IP。

6.2 申请 Let's Encrypt 免费证书

推荐使用 Certbot:

sudo apt update
sudo apt install certbot -y

申请前先停掉占用 80 端口的 Nginx 容器:

docker compose stop web
sudo certbot certonly --standalone -d your-domain.com
docker compose start web
注意--standalone 会临时占用 80 端口,与 Nginx 容器冲突,所以申请期间需要先停 web 容器。

6.3 配置 HTTPS

申请到证书后,修改 typecho.conf

server {
    listen 443 ssl;
    http2 on;
    server_name your-domain.com;

    ssl_certificate     /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    # ... 其余配置同 80 端口 ...
}

# 强制 HTTP 跳转 HTTPS
server {
    listen 80;
    server_name your-domain.com;
    return 301 https://$host$request_uri;
}

注意:Nginx 容器默认读不到宿主机的 /etc/letsencrypt 目录,需要在 docker-compose.ymlweb 服务里加一行挂载:

volumes:
  - /etc/letsencrypt:/etc/letsencrypt:ro

重启 Nginx 容器:

docker compose restart web

七、日常运维

7.1 查看日志与重启服务

# 实时查看 app 容器日志
docker compose logs -f app

# 实时查看 web 容器日志
docker compose logs -f web

# 重启某个服务
docker compose restart app

7.2 更新镜像并重建

# 拉取最新镜像
docker compose pull

# 根据新镜像重建容器
docker compose up -d

7.3 停止与清理

# 停止所有服务(保留数据和容器)
docker compose down

# 停止并删除容器(保留数据卷)
docker compose down --remove-orphans

# 停止并删除数据卷(危险操作,会清除所有数据!)
docker compose down -v

7.4 备份与恢复

备份数据库

# 确保备份目录存在
mkdir -p /opt/backups

# 读取 .env 变量
cd /opt/cms-server/typecho
set -a; source .env; set +a

# 备份数据库(-T 参数避免终端交互)
docker compose exec -T db \
  mysqldump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE" \
  > /opt/backups/typecho-db-$(date +%F).sql

备份数据目录

tar -czf /opt/backups/typecho-data-$(date +%F).tar.gz \
  /opt/cms-server/typecho/typecho-data \
  /opt/cms-server/typecho/mariadb-data

恢复数据库

# 先停止服务
docker compose down

# 导入备份(把文件名替换为你的备份文件)
docker compose exec -T db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" \
  < /opt/backups/typecho-db-2026-01-01.sql

# 重启服务
docker compose up -d

7.5 添加 Swap 分区(2C2G 服务器强烈建议)

2C2G 服务器同时运行 MySQL + PHP + Nginx 容易内存紧张,建议添加 2GB Swap 作为兜底:

# 创建 2G 交换文件
sudo fallocate -l 2G /swapfile

# 设置权限
sudo chmod 600 /swapfile

# 格式化为 swap 文件系统
sudo mkswap /swapfile

# 启用 swap
sudo swapon /swapfile

# 写入 fstab 实现开机自动挂载
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

验证:

# 查看内存和 swap 使用情况
free -h

# 查看 swap 设备状态
swapon --show

输出中应能看到 swap 行显示 2G 容量且已启用。


八、验收清单

检查项验证方法预期结果
容器运行状态docker compose ps三个容器均为 running 或 healthy
80 端口可访问浏览器访问 http://服务器IP显示 Typecho 安装页面
数据库连接安装向导填写数据库信息后点击安装安装成功,无连接错误
后台登录访问 http://服务器IP/admin可使用管理员账号登录
伪静态生效访问任意文章页面正常显示,不报 404
Swap 生效free -h能看到 swap 分区且容量为 2G
备份可用执行备份命令后检查文件/opt/backups/ 下有备份文件

附录 A:SSH 保活配置(可选)

SSH 连接在空闲一段时间后可能被云服务商或系统自动断开。可配置"心跳保活"机制。

sudo vi /etc/ssh/sshd_config

在文件底部插入:

ClientAliveInterval 60
ClientAliveCountMax 3

保存退出后重启 SSH 服务:

sudo systemctl restart ssh
# 或者
sudo systemctl restart sshd
注意:执行这条命令时,当前 SSH / SFTP 连接可能会断开,这是正常的,重新连接即可。

检查配置语法:

sudo sshd -t

没有报错输出即配置生效成功。


附录 B:常见问题

Q1:访问页面提示 403 Forbidden

检查 Nginx 配置中 root 路径是否正确指向 /app,以及 typecho-data 目录权限是否正确。

Q2:Typecho 提示无法连接数据库

确认 .env 文件中的数据库用户名和密码与安装向导中填写的一致;确认数据库服务名填的是 db 而不是 localhost

Q3:安装向导提示 config.inc.php 无法写入

执行 chown -R 33:33 typecho-data 修改目录属主,然后刷新页面重试。

Q4:容器启动后立刻退出

使用 docker compose logs <容器名> 查看容器日志排查具体错误原因。

Q5:Docker 拉取镜像超时或失败

检查镜像加速器配置是否正确,尝试更换其他镜像源地址。


附录 C:容器内 UID 确认方法

如果你使用的 Typecho 镜像不是 joyqi/typecho,PHP-FPM 的运行用户 UID 可能不同。可通过以下命令确认:

docker compose exec app id

输出类似:

uid=33(www-data) gid=33(www-data) groups=33(www-data)

其中的 33 就是需要设置的属主 UID。