今天想分享一下我的个人博客 kidd.weixiang.info 的搭建和演进历程。这不仅仅是一个技术分享,更像是一段长达三年的折腾记录,见证了我的博客从一个需要手动维护的“手工作坊”,一步步进化到如今使用 Docker Compose 实现一键部署、稳定运行的现代化应用。

楔子:梦开始的地方 (大约四年前)

四年前,我萌生了搭建一个个人博客的想法,用来记录技术笔记、分享生活感悟。在众多博客系统中,我被 Halo 的简洁、优雅和强大的功能所吸引。那时的我,还是一个对服务器运维懵懵懂懂的“新手村玩家”。

我选择了当时最主流,也是最“原始”的部署方式:

  1. 手动安装环境:登录到我的云服务器,手动安装 Java 运行环境 (JRE)。当时还得小心翼翼地配置环境变量,确保 java -version 能正确输出。

  2. 手动安装数据库:使用 yum 或 apt-get 安装 MySQL 数据库,手动创建名为 halo 的数据库,并为它授权用户。

  3. 下载并运行 Halo:从 Halo 官网下载最新的 .jar 包,通过 FTP/SCP 上传到服务器。然后用一行经典的命令启动它:

nohup java -jar halo-latest.jar > halo.log 2>&1 &
  1. 这行命令能让 Halo 在后台运行,并将日志输出到文件。为了让它开机自启,我还得研究 systemd,手写一个 service 文件。

  2. 手动配置 Nginx:为了使用域名和 HTTPS,我还需要安装 Nginx,然后手动编写 .conf 配置文件,配置反向代理,将 80/443 端口的流量转发到 Halo 运行的 8090 端口。SSL 证书也是手动申请,然后配置到 Nginx 中。

这种方式的痛点显而易见:

  • 部署复杂:每一步都需要手动操作,极易出错。

  • 环境依赖:应用与服务器环境深度绑定,换一台服务器就意味着所有步骤要重来一遍,还可能遇到各种环境不一致的坑。

  • 升级噩梦:每次 Halo 版本更新,我都需要 kill 掉旧的 Java 进程,下载新版 .jar 包,再重新启动。这个过程既繁琐又有风险。

  • 维护困难:数据备份需要手动执行 mysqldump,整个应用的状态分散在 JRE、MySQL、Nginx 和 Halo Jar 包四个部分,管理起来非常混乱。

转折点:拥抱 Docker,迈向新世界

随着我对容器化技术的了解加深,我意识到 Docker 正是解决上述所有痛点。Docker 可以将应用及其所有依赖打包到一个标准化的、可移植的“容器”中。这意味着,我的博客可以和它的运行环境(Java)、数据库(MySQL)一起,被轻松地打包、迁移和部署。

于是,我开始了我的博客容器化改造之路。这个过程并非一蹴而就,而是分阶段实现的:

  1. 第一阶段:先将 Halo 应用本身容器化,数据库和 Nginx 依然在宿主机上。

  2. 第二阶段:将数据库也容器化,使用 docker run 命令分别启动 Halo 和 MySQL 容器,并通过 --link(一个现在已不推荐的方式)或自定义 docker network 将它们连接起来。

  3. 第三阶段(最终形态):我发现了 docker-compose 这个编排神器。它允许我用一个 YAML 文件来定义和管理整个应用栈(Nginx、Halo、MySQL)。这,就是我现在正在使用的最终方案。

最终形态:Docker Compose 的一键魔法

现在,我的整个博客部署和运维工作,都收敛到了一个项目目录和一份 docker-compose.yaml 文件中。让我们来看看我当前的部署目录结构:

[root@harbor250.oldboyedu.com ~/halo]# ll
total 36052
# ... 其他文件 ...
-rw-r--r-- 1 root    root     2436 Aug  4 15:35 docker-compose.yaml
drwxr-xr-x 9 root    root     4096 Jul 31 22:16 halo2             # Halo 的持久化数据目录
-rw-r--r-- 1 root    root     1675 Aug  2 11:24 kidd.weixiang.info.key # SSL 证书私钥
-rw-r--r-- 1 root    root     3850 Aug  2 11:24 kidd.weixiang.info.pem # SSL 证书
drwxr-xr-x 9 polkitd root     4096 Aug  4 15:44 mysql             # MySQL 的持久化数据目录
drwxr-xr-x 2 root    root     4096 Jul 27 16:52 mysqlBackup       # MySQL 备份目录
drwxr-xr-x 2 root    root     4096 Aug  2 12:13 nginx             # Nginx 配置文件目录

所有配置和数据都清晰可见,备份和迁移只需要打包这个 halo 目录即可。

而这一切的核心,就是这份 docker-compose.yaml 文件。它像一份精美的“菜谱”,告诉 Docker 如何烹饪出我完美的博客应用。

version: '3'

services:
  # 服务1:Nginx 反向代理
  nginx:
    image: registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v1 # 使用一个稳定好用的Nginx镜像
    container_name: halo-nginx
    restart: on-failure:3
    depends_on:
      - halo # 确保在Halo启动后才启动Nginx
    networks:
      - halo_network
    ports:
      - "80:80"   # 暴露HTTP端口
      - "443:443" # 暴露HTTPS端口
    volumes:
      # 挂载Nginx配置文件,方便修改
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      # 挂载SSL证书,:ro表示容器内只读,更安全
      - ./kidd.weixiang.info.pem:/etc/nginx/ssl/kidd.weixiang.info.pem:ro
      - ./kidd.weixiang.info.key:/etc/nginx/ssl/kidd.weixiang.info.key:ro

  # 服务2:Halo 博客核心
  halo:
    image: registry.fit2cloud.com/halo/halo:2.21 # 明确指定Halo 2.21版本镜像
    container_name: halo
    restart: on-failure:3
    depends_on:
      halodb:
        condition: service_healthy # 高级依赖:确保数据库健康检查通过后再启动Halo
    networks:
      - halo_network:
    volumes:
      # 将Halo的工作目录挂载到宿主机,实现数据持久化
      - ./halo2:/root/.halo2
    healthcheck:
      # 健康检查,确保Halo应用本身正常运行
      test: ["CMD", "curl", "-f", "http://localhost:8090/actuator/health/readiness"]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 30s
    environment:
      # JVM内存配置
      - JVM_OPTS=-Xmx256m -Xms256m
    command:
      # 通过启动命令配置应用,完全告别配置文件
      - --spring.r2dbc.url=r2dbc:pool:mysql://halodb:3306/halo # 使用服务名halodb连接数据库
      - --spring.r2dbc.username=root
      - --spring.r2dbc.password=123456
      - --spring.sql.init.platform=mysql
      # 非常重要:配置正确的对外访问URL,以适配HTTPS
      - --halo.external-url=https://kidd.weixiang.info/

  # 服务3:MySQL 数据库
  halodb:
    image: crpi-qeqe5au7q3np1aih.cn-beijing.personal.cr.aliyuncs.com/weitianyu/wty_linux98:mysql-8.0.36
    container_name: halo-db
    restart: on-failure:3
    networks:
      - halo_network:
    command:
      # MySQL 启动参数
      - --default-authentication-plugin=caching_sha2_password
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_general_ci
      - --explicit_defaults_for_timestamp=true
    volumes:
      # 将MySQL数据目录挂载到宿主机,实现数据持久化
      - ./mysql:/var/lib/mysql
      # 额外挂载一个备份目录,方便在容器内执行备份操作
      - ./mysqlBackup:/data/mysqlBackup
    healthcheck:
      # 数据库健康检查,确保MySQL服务可用
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--silent"]
      interval: 3s
      retries: 5
      start_period: 30s
    environment:
      # 通过环境变量设置数据库,干净且安全
      - MYSQL_ROOT_PASSWORD=123456
      - MYSQL_DATABASE=halo

# 自定义网络,让三个服务在隔离的环境中通过服务名互相通信
networks:
  halo_network:

有了这份文件,现在我的所有操作都变得异常简单:

  • 首次部署:在新服务器上,只需安装 Docker 和 Docker Compose,然后将我的 halo 项目目录整个复制过去,进入目录,执行 docker-compose up -d,一杯咖啡的时间,我的博客就完美上线了。

  • 升级 Halo:只需修改 docker-compose.yaml 文件中 halo 服务的 image 标签,改成新版本号,然后执行 docker-compose up -d,Docker Compose 会自动拉取新镜像,并以平滑的方式替换掉旧的容器。

  • 备份:数据都在 mysql 和 halo2 目录中,写个简单的脚本 tar 一下这两个目录就行。或者直接 docker-compose exec halodb mysqldump ... 来执行数据库备份。

  • 迁移:把整个 halo 文件夹打包,复制到新服务器,再次 docker-compose up -d,我的博客就“搬家”成功了,无需任何额外配置。

总结与展望

从手动敲命令、配置文件的“石器时代”,到如今用一份 docker-compose.yaml 文件“代码化”定义整个应用的“现代文明”,这三年的演进,不仅是我的博客部署方式的升级,更是我个人技术理念的转变。我深刻体会到了“基础设施即代码”(IaC)的强大魅力:自动化、标准化、可移植性。

这段经历告诉我,好的工具和方法论能够将我们从繁琐、重复的劳动中解放出来,让我们更专注于创造性的工作——比如,安安心心地写好每一篇博客。

如果你也正在或准备搭建个人项目,我强烈推荐你从一开始就拥抱 Docker 和 Docker Compose。它在前期的学习投入,将在后续的开发、部署和维护中,为你节省下无数的时间和精力。