Administrator
发布于 2025-11-17 / 74 阅读
0
0

📄 Halo 博客 Docker Compose 部署全纪录

1. 🎯 目标与技术选型

  • 目标: 部署一个生产环境可用的 Halo 博客。
  • 应用: Halo v2.21+。
  • 数据库: PostgreSQL v15.4 (遵循官方推荐)。
  • 部署工具: Docker Compose (用于编排 Halo 和 PostgreSQL 两个容器)。
  • 网络: 腾讯云服务器 + Cloudflare (提供 HTTPS 和 DNS)。

2. 🚀 初始配置与启动

我们首先创建了一个 my-halo-blog 目录,并在其中创建了 docker-compose.yml 文件。(这是我们使用的,但有问题的“V1”版本配置)

# version: "3"  <- (此行已过时,后被删除)
​
services:
  halo:
    image: registry.fit2cloud.com/halo/halo:2.21
    # ... (省略健康检查等) ...
    volumes:
      - ./halo2:/root/.halo2
    ports:
      # 🔴 错误点 1:我们最初假设 Halo 在 8080 端口运行
      - "8080:8080"
    command:
      - --spring.r2dbc.url=r2dbc:pool:postgresql://halodb/halo
      - --spring.r2dbc.username=halo
      - --spring.r2dbc.password=***
      # 🔴 错误点 2:使用了 localhost 作为外部 URL
      - --halo.external-url=http://localhost:8080/
  halodb:
    image: postgres:15.4
    # ... (省略) ...
    volumes:
      - ./db:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=***
      - POSTGRES_USER=halo
      - POSTGRES_DB=halo
# ... (省略 networks 和 volumes) ...

3. 💣 故障排查(一):503 错误与 external-url

我们启动容器后,通过 http://[公网IP]:8080 访问,遇到了 503 Service Unavailable 错误。

  • 诊断: 我们检查了 docker-compose.yml,发现 external-url 被设置为了 http://localhost:8080/
  • 原因: external-url 是 Halo 用来验证访问来源是否合法的。当它被设为 localhost 时,所有来自公网 IP 的访问都会被拒绝。
  • 解决方案: 我们将 external-url 修改为服务器的公网 IP 地址。

4. 💣 故障排查(二):ERR_SSL_PROTOCOL_ERROR (Cloudflare 错误)

在我们将 external-url 改为 http://blog.awic.cloud:8080/ 并配置了 Cloudflare(橙色云代理)后,浏览器报错 ERR_SSL_PROTOCOL_ERROR

  • 诊断: 这是 Cloudflare 的 SSL 模式与我们的服务器配置冲突。
  • Cloudflare (访客 -> CF): Cloudflare 正在为我们提供 https:// (SSL)。
  • Halo (CF -> 服务器): 我们的 Halo 容器只在 8080 端口提供 http:// (非 SSL)。
  • 原因: Cloudflare 试图用 HTTPS 协议去连接一个只懂 HTTP 的 8080 端口,导致SSL握手失败。
  • 解决方案 (B - 推荐):
    • Cloudflare 端: 设置 SSL/TLS > 概述 为 "Flexible" (灵活) 模式,并开启 SSL/TLS > 边缘证书 > "Always Use HTTPS" (始终使用 HTTPS)。
    • 服务器端 (配置): 我们修改 docker-compose.yml,决定让 Cloudflare 通过标准 80 端口来访问我们。
    • ports 修改为: - "80:8080" (🔴 这是一个基于错误假设的修改,我们以为 Halo 内部在 8080)。
    • external-url 修改为: - --halo.external-url=https://blog.awic.cloud/ (正确)。

5. 💣 故障排查(三):Error 521 (Web 服务器宕机)

在执行完上一步并 docker compose up -d 后,浏览器不再报 SSL 错误,而是显示了 Cloudflare 的 Error 521 错误页。

  • 诊断: 521 错误意味着 Cloudflare (作为访客) 无法连接到我们的源服务器 (101.33.238.33)。
  • 原因: 我们的“解决方案 B”让 Cloudflare 通过 80 端口访问。但我们的**腾讯云安全组(防火墙)**默认只开放了 808022 等,没有开放 80 端口。
  • 解决方案: 登录腾讯云控制台,编辑安全组“入站规则”,添加一条规则,允许 TCP:80 端口的流量。

6. 💣 故障排查(四):unhealthy 与“端口终极解谜”

在开放了 80 端口后,浏览器访问仍然失败。我们回到服务器检查。

  • 诊断 (1): docker ps 显示 halo-1 容器的状态为 (unhealthy)(不健康)。
  • 诊断 (2): curl -I http://127.0.0.1 (测试服务器的 80 端口) 报错 Connection reset by peer

这是最关键的突破点:

  • 推论: 容器“不健康”意味着 healthcheck 失败了。我们的 healthcheck 正在检查 http://localhost:8080/。这说明 Halo 应用在容器内部根本不在 8080 端口!
  • 验证: 我们运行 docker compose logs halo,在海量的启动日志中,我们找到了这一行:
    halo-1 | ... NettyWebServer : Netty started on port 8090 (http)
    
  • 真相大白: Halo 程序的开发者(Spring Boot 配置)规定了程序在容器内的**8090**端口上运行。而我们一直在错误地假设它是 8080

7. 🚀 最终的正确配置与启动

我们必须修改 docker-compose.yml,使我们的“指示牌”与 Halo 的“真实房间号”相匹配。

  1. docker compose down 停止并删除旧容器。
  2. nano docker-compose.yml 修改配置。
  3. docker compose up -d 重新启动。

这是最终的、100% 正确的 docker-compose.yml 配置:

# 'version' 属性已过时,已被删除
services:
  halo:
    image: registry.fit2cloud.com/halo/halo:2.21
    restart: on-failure:3
    depends_on:
      halodb:
        condition: service_healthy
    networks:
      - halo_network
    volumes:
      # 将宿主机的 ./halo2 目录挂载到容器内,实现 Halo 数据持久化
      - ./halo2:/root/.halo2
    ports:
      # ✅ 正确的映射:
      # 将宿主机的 80 端口 (Cloudflare 正在访问) 映射到 Halo 容器的 8090 端口 (Halo 真正监听的)
      - "80:8090"
    healthcheck:
      # ✅ 正确的健康检查:
      # 在容器内部,检查 8090 端口
      test: ["CMD", "curl", "-f", "http://localhost:8090/actuator/health/readiness"]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 30s
    environment:
      - JVM_OPTS=-Xmx256m -Xms256m
    command:
      - --spring.r2dbc.url=r2dbc:pool:postgresql://halodb/halo
      - --spring.r2dbc.username=halo
      # 🔴 确保这个密码与 halodb 的密码一致
      - --spring.r2dbc.password=***
      - --spring.sql.init.platform=postgresql
      # ✅ 正确的外部 URL:
      # 必须是 https 协议,并且不带端口(因为 Cloudflare 帮我们处理了)
      - --halo.external-url=https://blog.awic.cloud/
​
  halodb:
    image: postgres:15.4
    restart: on-failure:3
    networks:
      - halo_network
    volumes:
      # 将宿主机的 ./db 目录挂载到容器内,实现数据库持久化
      - ./db:/var/lib/postgresql/data
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 10s
      timeout: 5s
      retries: 5
    environment:
      # 🔴 确保这个密码与 halo 的密码一致
      - POSTGRES_PASSWORD=***
      - POSTGRES_USER=halo
      - POSTGRES_DB=halo
      - PGUSER=halo
​
networks:
  halo_network:
​
volumes:
  # 声明 volumes 以便 Docker 管理 (虽然我们用了绑定挂载,但这是个好习惯)
  # 在这个配置里,我们使用的是绑定挂载(./db 和 ./halo2),所以 volumes 块不是必需的,但保留亦可。

8. 🏁 成功

重启后,docker ps 显示 halo-1 状态为 (healthy)。访问 https://blog.awic.cloud,Halo 博客成功显示,并带有 SSL 安全锁。部署完成。


评论