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端口访问。但我们的**腾讯云安全组(防火墙)**默认只开放了8080和22等,没有开放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 的“真实房间号”相匹配。
docker compose down停止并删除旧容器。nano docker-compose.yml修改配置。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 安全锁。部署完成。