☰
Docker私有化部署Halo博客:从零到HTTPS完整指南
2026/9/30 17:37:12 网站建设 项目流程

标题里我把话就说完了:这是我在自己的服务器上用Docker部署的Halo博客系统,已经稳定跑了两年多。私有化部署听起来门槛很高,但真上手你会发现,它比你在某个平台注册个号开始写文章还快,而且从第一行代码到最后一篇随笔,所有数据都握在自己手里。今天就把整套流程从零讲透,包括怎么选服务器、装Docker、启动Halo、绑域名、上HTTPS、备份迁移,以及我踩过的所有坑。

这篇内容适合两类人:一是受够了各种写作平台广告和审核规则,想拥有一个完全受自己控制的博客的写字的人;二是对Docker有兴趣但一直没找到适合练手项目的开发者。Halo恰好是同时满足这两类需求的解——它是一个Java写的开源博客系统,API干净、插件生态成熟、官方默认主题走极简路线,配合Docker的部署方式,就算你完全没接触过Linux,照着操作也能把博客跑起来。

1. 为什么最终选了 Halo + Docker

1.1 从“写在平台上”到“写给自己”

先说个扎心的事实:你在公众号、知乎、或者各种笔记App里写了几年内容,哪天平台改规则、关功能、甚至直接停服,你的文字和数据要迁出来非常麻烦。我自己就经历过一次笔记软件停服,导出数据折腾了整整一个周末,从那以后就铁了心要搞一个自己的博客。

私有化部署解决的就是这个“数据主权”问题。服务器是你租的,数据库在你机器上,文章导出导入随时可以做,想改样式改栏目改接口都行。这种感觉和借住在别人家完全不同——你只是在互联网上租了一小块地皮,但这块地皮上的房子,产权完全是你自己的。

当然,自己搭博客也意味着服务器费用、维护成本、安全补丁都得自己管。但如果你只在上面跑一个Halo,一台2核2G的云服务器绰绰有余,一年的费用甚至比很多写作软件的会员费还便宜。这笔账怎么算都划算。

1.2 对比了一圈:WordPress太重,静态博客太折腾

选型阶段我把主流方案都过了一遍,简单说说当时的判断,给还在纠结的你做个参考。

WordPress功能确实全面,主题插件多到数不清,但问题恰恰出在“全面”上:默认编辑器体验一般,插件装多了页面加载越来越慢,还时不时要处理安全更新和小漏洞。再加上我身边不止一个朋友被各种花哨主题折腾到怀疑人生,我直接划掉了这个选项。

静态博客(Hexo、Hugo这类)也很心动,渲染出来的页面那叫一个干净,部署也简单,扔到对象存储或者GitHub Pages上就能访问。但静态博客的痛点在于“写”的体验:没有后台编辑器,通常得在本地写好Markdown再跑命令生成、推送,手机端基本没法用。我写东西经常是碎片时间,这个工作流对我来说太不友好了。

Halo是我试了一圈之后留下的答案。它默认就是一个带后台的博客系统,打开浏览器就能写文章,Markdown体验顺畅,默认主题走的是清爽路线。而且因为基础是Java生态,运行非常稳定,不像某些轻量方案跑着跑着就内存溢出了。最关键的是它支持插件机制,需要评论、图床、统计之类的功能,装一个插件就行,不需要自己改代码。

1.3 Docker 让“部署”变得像“装App”一样简单

聊完博客方案,再聊部署方式。很多人一听“部署”两个字就头大,其实Docker已经把门槛压得非常低了。

你可以把Docker理解成集装箱运输:镜像(Image)就是标准化的货物,容器(Container)就是正在运行的货物实例,而数据卷(Volume)是挂在外面的货柜,怎么搬都不会丢。有了这套抽象,你不再需要关心服务器上装的是哪个操作系统、缺哪个依赖库、Java版本是不是匹配,这些在镜像里全都打包好了。

用Docker部署Halo有个特别实在的好处:升级和回滚都非常干净。想升级博客版本,就拉一个新版本的镜像,停旧容器起新容器;万一升级出问题了,把旧镜像的标签换回来,几分钟就回滚了。如果是传统那种直接在服务器上装Java、装数据库、再跑一个进程的方式,升级一次往往心惊胆战,不敢轻易动。

另外一个隐藏优势是环境的一致性。你在本地测试能跑通的配置,推到服务器上通常也能跑通,因为Docker镜像屏蔽了底层系统的差异。这个特性对新手来说太重要了,意味着网上教程里的命令可以直接抄作业,不用在“为什么我的Ubuntu跑不起来”这个问题上浪费半天。

2. 部署前你要心里有数的几件事

2.1 服务器选型和系统准备

先说最低配置结论:个人博客用Halo,2核2G的云服务器完全够。我的博客跑了两年多,日常内存占用不到1G,CPU更是很少有波动。如果你打算同时跑多个应用,比如还要挂一个Vaultwarden或者监控面板,那可以升到2核4G,但纯粹写博客,2G已经绰绰有余。

操作系统我推荐Ubuntu 22.04 LTS或者Debian 12,这两个系统的Docker支持最省心。选系统的时候注意两点:一是不要选“已停止维护”的老版本,比如CentOS 7这类,已经过了生命周期,装新版本Docker会遇到各种源的问题;二是建议选带公网IP的独立服务器,而不是只给你一个内网地址的虚拟主机,否则后面域名绑定会麻烦很多。

服务器到手之后,第一步是更新系统包,然后创建自己的登录用户,不建议直接用root干活,万一手指一抖输错命令,影响面太大。我习惯创建一个叫deploy的用户,把需要用到的目录权限都给它,日常操作就用这个账号。

2.2 安装 Docker 环境的正确姿势

Docker安装方式有几种,我只推荐最稳妥的官方脚本安装方式:

curl -fsSL https://get.docker.com | sh

这行命令会自动检测系统环境,配置好Docker官方软件源并安装最新社区版。安装完成后先别急着用,执行一下这个命令确认没有白装:

docker version

能看到Client和Server两行信息就说明Docker服务已经正常跑起来了。如果只有Client没有Server,试试启动服务:

sudo systemctl enable --now docker

这里有个新手几乎必踩的坑:默认情况下只有root或者sudo权限才能执行docker命令,每次敲命令都加sudo真的很烦。解决办法是把当前用户加入docker组:

sudo usermod -aG docker $USER newgrp docker

重新登录之后再执行docker version就不需要加sudo了。注意,加入docker组等同于给了这个用户很高的系统权限,因为Docker本身能挂载目录、接管网络,所以这个操作最好只在自己日常用的账号上做,不要给多人共享的服务器账号乱加。

2.3 动手前必须理解的三个概念

在敲第一条部署命令之前,我建议你先花五分钟弄清楚三个核心概念,省得后面出问题一脸懵。

第一是镜像和容器的区别。镜像是一份只读的模板,容器是镜像运行起来后的实际进程。你可以把镜像理解成手机App的安装包,容器就是App安装好之后在桌面上运行的那个图标。我们从一个镜像可以创建多个容器,互不影响。

第二是数据卷的挂载。容器内部的文件系统是临时的,删除容器数据就没了,所以必须把重要数据目录挂载到宿主机上。比如Halo在容器里的工作目录是/root/.halo2,我们把它映射到服务器的/opt/halo目录,这样即使容器删了重建,数据和附件都还在宿主机上,这就是“数据不丢”的底气。

第三是端口映射。Halo容器内部监听8090端口,但这个端口只在容器内部,要让外部访问,必须把它映射到宿主机的端口上,也就是启动命令里的-p 8090:8090。前面那个8090是宿主机端口,外面访问的是它;后面那个8090是容器端口,Halo进程监听的地址。搞懂这三个概念,后面无论部署什么应用,你都能知道命令里每个参数在干什么。

3. 用Docker把Halo跑起来,全流程实操

3.1 目录规划和端口规划

正式部署前花两分钟做个规划,能省掉后面很多麻烦。

我习惯把所有Docker应用的数据都放到一个统一目录下,比如/opt,然后按应用名字分子目录。Halo的数据目录就定为/opt/halo,这里面会放Halo的配置、H2数据库文件、附件、主题和插件,相当于整个博客的家当都在这一个目录里。

端口方面,Halo默认监听8090端口,这也是官方文档的统一约定。除非8080或8090已经被其他服务占用,否则不建议换端口——换了虽然也能用,但很多文档、插件、反向代理配置都是按默认端口写的,保持一致能让查问题时少一个变量。

还有一个很多人忽略的点:云服务器的安全组。如果你用的是阿里云、腾讯云这类服务商,控制台里的安全组规则默认可能只放行了80、443和22端口。就算你在服务器上把8090端口开放了,安全组没放行,外部依然访问不了。所以在启动Halo之前,先去云控制台确认一下8090端口(或者你最终对外暴露的端口)已经在安全组规则里放行。

3.2 一条 docker run 命令把博客拉起来

规划好了就动手。我的启动命令是这样的:

docker run -d \ --name halo \ -p 8090:8090 \ -v /opt/halo:/root/.halo2 \ --restart=always \ halohub/halo:2.x

逐条解释一下,不是让你死记硬背,而是让你知道每个参数到底在干什么。

-d是让容器在后台运行,不会占着你的终端。--name halo给容器起个名字,以后启停、查看日志都靠这个名字。-p 8090:8090就是刚才说的端口映射。-v /opt/halo:/root/.halo2把宿主机的数据目录挂载进容器的工作目录,这是数据持久化最关键的一步。--restart=always表示服务器重启后Docker会自动把Halo容器带起来,省的服务器一重启博客就断线,你还要手动登录去启动。

关于镜像版本,这里特别提醒一下:安装命令里的2.x要替换成实际的版本号,建议去Halo官方仓库页面看一眼最新的稳定版本标签。比如当前稳定版是2.20.x,就写halohub/halo:2.20。不建议直接用latest标签,因为无法预期它什么时候更新,生产环境更要避免这种不确定性。

命令执行成功后,输入docker ps应该能看到halo容器处于Up状态。第一次启动需要一点时间初始化数据库和默认配置,大约十秒左右,然后打开浏览器访问http://你的服务器IP:8090,就能看到Halo的初始化页面了。

初始化页面会让你设置管理员用户名和密码,以及博客的标题。注意密码设置页面会要求一定复杂度,但不用设置得像银行账户那样变态,毕竟你后面还可以绑HTTPS、设置登录防护插件。初始化完成后,你会进入一个非常简洁的后台界面,这就是接下来每天写作的地方。

3.3 认真推荐:用 docker compose 管理你的部署

docker run一条命令确实能跑起来,但如果你打算长期用,我强烈建议升级到docker compose的方式。它本质上是把容器的所有配置写进一个YAML文件里,好处是显式、可版本管理、改动可控。

在/opt/halo目录下新建一个docker-compose.yml文件,内容如下:

services: halo: image: halohub/halo:2.20 container_name: halo restart: always ports: - "8090:8090" volumes: - /opt/halo:/root/.halo2

保存后,在同一个目录执行:

docker compose up -d

效果和刚才的docker run完全一样,但以后所有改动都围绕这个文件展开。升级版本时,把image那一行的版本号改一下,然后执行:

docker compose pull docker compose up -d

命令会自动拉新镜像、重建容器,并且因为数据卷挂载没变,所有文章数据原样保留。删除容器也更干净,一条docker compose down就能把容器移除,数据目录还在,随时可以重新拉起来。

我第一次部署时图省事用的docker run,后来要加环境变量、调整参数,发现那串命令越来越长,改起来头疼。换成compose之后,所有配置都摊在文件里清清楚楚,哪怕是半年后再来看,也知道当初到底怎么部署的。

4. 极简博客的“标配”:域名、HTTPS与访问优化

4.1 从“IP:8090”变成自己的域名

博客跑起来之后,用IP加端口访问总觉得不是那么回事,而且8090端口真的不够好看。配一个域名,让博客看起来像“正式项目”,这个步骤建议尽早做。

先去域名注册商那里买一个域名,一年几十块钱的就够用,不追求什么特殊后缀,com、net、top都行。买完之后在域名解析控制台添加一条A记录,主机记录填www或者@,记录值填你的服务器公网IP,TTL用默认值。

然后回到服务器上,确认80端口和443端口是放行的。域名解析生效后先别急着配HTTPS,用http://你的域名试试能不能访问到Nginx默认页,如果响应了,说明域名到服务器的通路已经通了。

4.2 用Nginx做反向代理,把博客藏到标准端口后面

有了域名之后,下一步就是把外部访问统一收敛到80/443端口,再由Nginx反向代理转发到容器内部的8090。这样做的理由有两个:一是80/443是访问者默认预期的端口,不需要在网址里手动敲:8090;二是后续挂HTTPS证书,Nginx是承担证书生命周期的理想位置,不用改Halo容器本身。

Nginx可以装在宿主机上,也可以再起一个Nginx容器。我个人倾向于宿主机直接装一个,因为Nginx本身非常轻量,而且宿主机层的配置省去一层容器间网络通信的复杂度。

sudo apt install nginx

然后在/etc/nginx/conf.d/下新建一个halo.conf文件,写入:

server { listen 80; server_name your-domain.com; client_max_body_size 1024m; location / { proxy_pass http://127.0.0.1:8090; 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 $scheme; } }

client_max_body_size这个配置容易被忽视,它的作用是允许上传大附件,比如你传一个几十MB的图片或者PDF,如果默认的1M限制没改,上传会直接报错。我一开始没设这个参数,后来传一张手机原图就被Nginx拒绝了,排查了半天才想起来是上传大小限制的问题。

改完配置后执行:

nginx -t sudo systemctl reload nginx

配置文件没问题的话,现在就可以通过http://你的域名访问博客了。

接下来是HTTPS证书。推荐用acme.sh这种方式签免费证书,步骤大概是:

curl https://get.acme.sh | sh source ~/.bashrc ~/.acme.sh/acme.sh --issue -d your-domain.com -w /var/www/html --nginx

签发成功后,把证书安装到Nginx目录:

~/.acme.sh/acme.sh --install-cert -d your-domain.com \ --key-file /etc/nginx/cert/your-domain.com.key \ --fullchain-file /etc/nginx/cert/your-domain.com.pem \ --reloadcmd "systemctl reload nginx"

然后更新之前那个halo.conf,加上443监听和证书路径:

server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/nginx/cert/your-domain.com.pem; ssl_certificate_key /etc/nginx/cert/your-domain.com.key; client_max_body_size 1024m; location / { proxy_pass http://127.0.0.1:8090; 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 $scheme; } } server { listen 80; server_name your-domain.com; return 301 https://$host$request_uri; }

最后把域名解析换成HTTPS,以后访问博客就是一把小锁头加绿色地址栏,写作和访客浏览都更放心。

4.3 备份与迁移:私有化最容易被偷懒但最不能偷懒的事

有了自己的数据,就得想清楚备份策略。我原来也觉得“服务器不会出事吧”,直到有次手滑删了目录,才真正明白什么叫欲哭无泪。后来我给自己定了一条铁律:每周全量备份一次数据目录,动任何配置之前先手动备份一次。

Halo的数据备份非常简单,因为所有东西都在/opt/halo这个目录里。数据库文件、附件、主题、插件配置全都在里面,你只需要打包这一个目录:

tar -czvf halo-backup-$(date +%F).tar.gz /opt/halo

把这个tar包同步到对象存储或者另一台机器上即可。如果你有对象存储,配合restic或者rclone这类工具可以做增量备份和自动同步,我的做法是用cron每周日凌晨跑一个脚本,把备份推到对象存储,日志发到Telegram,这样连“我忘了备份”这件事都不会发生。

迁移到新服务器就更简单了:新机器装好Docker,把备份包解压到/opt/halo,再启动一个同样参数的Halo容器,访问新地址,博客连附件带插件全部恢复如初。整个过程通常不超过十分钟,这就是Docker加数据卷挂载最大的红利。

5. 常见问题与排障速查表

5.1 镜像拉取太慢或拉取失败

这个问题几乎所有国内用户都会遇到。默认从Docker Hub拉取镜像的速度很不稳定,解决办法是配置镜像加速器。

在/etc/docker/daemon.json里添加镜像加速器地址:

{ "registry-mirrors": ["https://你的加速器地址"] }

国内主流云厂商都提供免费的容器镜像加速服务,注册后就能拿到专属地址。配置好之后执行:

sudo systemctl daemon-reload sudo systemctl restart docker

然后重新拉镜像,速度会有非常明显的提升。

5.2 端口访问不通,思路全乱

端口不通的排查顺序非常固定,按顺序查一遍基本就能定位。

第一步查容器状态,docker ps看容器是不是Up状态,如果在Restarting,看日志找原因。第二步在服务器本地试试curl http://127.0.0.1:8090,如果能返回页面,说明容器没问题,问题出在外部链路上。第三步查防火墙,sudo ufw status看看有没有把8090端口放行。第四步查云控制台的安全组规则,这个最容易被忽略,很多服务商默认不放开非标准端口。

这几步走完,90%的端口问题都能定位。我的经验是,绝大多数“部署成功但访问不了”的问题,都出在安全组或者防火墙上面,跟Halo本身没什么关系。

5.3 忘记后台密码

Halo没有提供像WordPress那样方便的“忘记密码”邮件重置链接,万一密码忘了,最稳妥的办法是直接操作数据库。

Halo默认的H2数据库文件就在/opt/halo/db目录下。用h2.jar工具连接数据库,找到用户表,把密码字段替换成新生成的BCrypt哈希值即可。生成BCrypt哈希可以用Halo的encode命令,比如进入容器执行:

docker exec -it halo /opt/halo/bin/halo encode --password yournewpassword

拿到输出的一长串哈希之后,再到数据库里更新对应用户的password字段,重启容器,新密码就能用了。

说实话这个操作有点门槛,所以更建议的做法是:定期备份数据库,忘记密码时干脆恢复一次数据备份,或者如果有管理权限,直接新建一个管理员账号再删掉旧的。

5.4 升级版本后网站打不开/插件不兼容

升级Halo版本理论上很平滑,因为官方做了数据库自动迁移,但插件生态偶尔会拖后腿。升级完发现页面白屏,先看容器日志:

docker logs halo

日志里通常能看到具体的异常堆栈,如果指向某个插件,最直接的解决办法是停用那个插件(可以通过数据库修改插件状态),或者先降级回旧版本等插件适配。

为了应对这种情况,升级前务必做好三件事:备份/opt/halo整个目录、确认新版镜像tag存在、留出十分钟的空档期。有备份在手,升级这件事的风险就完全是可控的。

6. 把Halo调教成真正“极简”的写作工具

6.1 主题能少装就少装

Halo的主题商店里确实有不少漂亮的主题,但你真开始写东西之后会发现,花里胡哨的样式很快就腻了。我目前用的还是官方默认主题,改了一下页面宽度和字号,整体就很好看了。

默认主题的排版逻辑是内容优先,标题、正文、代码块、引用都处理得很清楚,没有多余的装饰。对于写作来说,这个状态其实是最好的——你不会被页面样式分散注意力,访客也能把焦点放在文字本身。

6.2 插件只装踩坑之后真需要的那几个

插件也是一样的逻辑。Halo的插件机制很强,但不需要一开始就全都装上。我的建议是先裸跑一个月,过程中产生明确需求了再装。比如你发现评论区被垃圾消息刷屏了,再装评论防护插件;你觉得后台默认编辑器不够顺手,再换Markdown编辑器插件。

我现在生产环境上只有三个插件:一个是评论系统增强,一个是定时备份,一个是代码高亮。这个数量级下,整个后台打开速度极快,也不用担心插件之间的兼容性问题。

6.3 写作工作流:以Markdown为核心

Halo的编辑器天然支持Markdown,你可以直接在后台写,也可以先用本地的Typora这类工具写好再粘贴进去。我的习惯是长文先在本地写,用Markdown把结构打好,再复制到Halo的编辑器里微调一下格式。短文就直接在后台写,打开页面就能敲字,不用开多余的App。

如果你经常用手机记录灵感,Halo也有对应的移动端适配,后台界面在手机上浏览基本可用,写短文完全没问题。总之整个工作流非常轻,从打开后台到发布一篇文章,五分钟以内肯定能完成。

6.4 坚持写作才是博客的终极维护

跑到现在这个节点,技术层面的工作其实已经结束了——服务器稳定、HTTPS没问题、备份在自动跑。接下来最难的只有一件事:持续写。我自己的体会是,把博客当成一个陪自己成长的记录本,而不是一个需要运营的“产品”,反而能坚持得更久。偶尔一个月写一篇也好,一年后回看那些记录,会发现那些琐碎的思考其实已经拼出了一条清晰的轨迹。

这就是Docker私有化部署Halo最大的意义:把所有技术门槛都压到最低,让你只需要面对真正重要的事情——写下去。希望这篇文章能帮你把博客跑起来,也希望你能比我更早地体会到“拥有自己领地”的踏实感。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询