服务器部署静态博客私有朋友圈-上

1. 前言
我为什么要选择静态博客框架来部署一个站点呢,主要是因为我希望使用markdown格式来记录一下日常琐事与心得体会 。md文件存储的方式,日后可以迁移到其他平台,不必被平台绑定死;md文件的存储,可以做很多有趣的python脚本程序批处理,比如做批量增删改查或者做展示应用。
按照这个要求,如果是有后台的博客框架,就要使用数据库就没有办法使用markdown了(其他强大的动态博客可以使用插件解决,但是不考虑)。本人看到过一款朋友圈风格的主题,用来记录生活的文章呈现效果非常漂亮(九宫格图片自动排版),查找后发现hugo框架有moments主题,那就二话不说部署一个私有朋友圈给自己使用。
选择hugo的好处:静态博客只需要配置一下文件,运行构建就完成对md文件的渲染并生成静态文件,之后就不需要后台运行,非常省资源。
选择moments主题的好处:我很早就喜欢朋友圈/微博风格的短文博客,看着非常喜欢。对于含有多张图片展示,它可以帮你自动排版(最多九张),简直好看好用。可以把那些在微信朋友圈或者微博上不能言说的言语或者灵光一现的有趣想法,在moment记录下来。
为什么不使用memos:因为memos只是卡片备忘录,不适合记录日常生活,不支持md文件。

2. 部署Hugo
2.1 部署Hugo分析
hugo静态博客需要搭配主题使用,主题下载在gohugo.io官方。有了主题并配置好配置文件,就运行构建命令就可以完成博客的构建,这里我们采用hugo容器进行构建。
目前我们使用Hugo容器有两种运行方式,一种是服务端常驻后台运行(它提供Nginx服务),一种是只运行一次(它运行构建后退出)。我们使用构建模式运行方式,那么就要搭配nginx容器完成网页服务。构建方式的启动,我们使用webhook方式触发构建脚本,脚本会拉取github仓库的md文件并运行构建命令。最后访问nginx服务就可以访问托管的网页了。
hugo镜像选择:


moment主题:


2.2 部署脚本
hugo的文件目录架构:



一些镜像版本的选择要求与容器命令使用方法:


编排docker-compose:
version: '3'
services:
# 1. Hugo Server服务(开发模式,实时预览;生产模式时webhook触发构建)
# hugo-server:
# image: klakegg/hugo:ext-alpine # moments主题要求,ext-alpine
# container_name: hugo-server
# volumes:
# - /root/data/docker_data/hugo/hugo-data:/src
# ports:
# - "1313:1313"
# command:
# [
# "server", # 启动Hugo开发服务器
# "--theme", "moments", # 指定主题
# "--source", "/src", # 工作目录
# "--bind", "0.0.0.0", # 允许外部访问(必须,否则宿主机无法访问)
# "--liveReloadPort", "1313", # 热重载端口
# "--disableFastRender", # 禁用快速渲染,确保修改实时生效
# "--verbose" # 输出详细日志,方便调试
# ]
# restart: unless-stopped
# networks:
# - default
# - my_npm_prj_npm_network
# Webhook容器:仅写触发标记,触发脚本不在容器内执行
webhook:
image: almir/webhook:latest
container_name: hugo-webhook
user: root #以root用户运行容器,非webhook用户
volumes:
- ./webhook/hooks:/etc/webhook #touch hooks.json
- ./webhook/trigger:/trigger # 新建共享目录:写触发标记文件
- ./webhook/scripts:/scripts # 脚本
# ports:
# - "9000:9000"
networks:
- default
- my_npm_prj_npm_network
command: -hooks=/etc/webhook/hooks.json -verbose
restart: unless-stopped
# Nginx容器:托管静态页(可不使用该服务,如果使用npm加path进行反代)
nginx:
image: nginx:alpine
container_name: hugo-nginx
volumes:
- ./hugo-data/public:/usr/share/nginx/html:ro #指向public构建目录
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro #保留主配置
# ports:
# - "8033:80"
networks:
- default
- my_npm_prj_npm_network
restart: unless-stopped
networks:
my_npm_prj_npm_network:
external: true
目录文件结构:


hugo-build.sh是构建脚本;
hugo-build.log是构建脚本的输出日志重定向文件;
get-hugo-theme-moments.sh是下载moments主题脚本;
# 进入Hugo工程根目录
cd /root/data/docker_data/hugo/hugo-data
# 克隆moments主题到themes/moments目录
git clone https://github.com/FarseaSH/hugo-theme-moments.git themes/moments
my文件夹是存放自己下载好的自定义背景图;
构建脚本:
这里我们不使用webhook容器执行构建,因为webhook容器里没有git docker等环境。
#!/bin/bash
# 开启错误立即退出,避免脚本执行一半出错
set -e
# Hugo工程根目录(存放content/themes/public等)
DATA_DIR="/root/data/docker_data/hugo/hugo-data"
# Webhook触发标记文件路径
TRIGGER_FILE="/root/data/docker_data/hugo/webhook/trigger/build.flag"
# Hugo主题名称(和themes目录下的文件夹名一致)
HUGO_THEME="moments"
# GIT私有仓库
GIT_USER="haiou-user"
GIT_REPO_NAME="my-mome-posts"
GIT_TOKEN="ghp_MAGdZnM2LUFShFapBdlLETdIMbiu"
# Git仓库 MD文章仓库地址
GIT_REPO="github.com/${GIT_USER}/${GIT_REPO_NAME}.git"
GIT_CLONE_REPO_URL="https://${GIT_USER}:${GIT_TOKEN}@${GIT_REPO}"
# 检测是否有触发标记
if [ -f "$TRIGGER_FILE" ]; then
echo "========================================"
echo "$(date +'%Y-%m-%d %H:%M:%S') - 检测到构建触发标记,开始执行宿主机构建..."
echo "========================================"
# ========== 宿主机Git拉取 ==========
echo "step1,开始拉取GitHub MD文章仓库..."
# 确保DATA_DIR目录存在
mkdir -p "$DATA_DIR"
if [ ! -d "$DATA_DIR/content" ]; then
echo " - 首次拉取,克隆仓库到 $DATA_DIR/content..."
git clone "$GIT_CLONE_REPO_URL" "$DATA_DIR/content"
else
echo " - 已有仓库,拉取最新MD文章..."
cd "$DATA_DIR/content" && git pull
fi
# ========== 宿主机执行Hugo构建 ==========
echo "step2,开始执行Hugo构建静态页..."
docker run --rm
--entrypoint hugo
-v "$DATA_DIR:/src"
klakegg/hugo:ext-alpine
--theme "$HUGO_THEME" --source /src --destination /src/public
# ========== 删除触发标记,避免重复构建 ==========
echo "step3, 删除触发标记文件..."
rm -f "$TRIGGER_FILE" #调试时屏蔽,不执行删除
echo "$(date +'%Y-%m-%d %H:%M:%S') - 宿主机构建流程全部完成!"
echo " - 静态页输出路径:$DATA_DIR/public"
echo " - MD文章路径:$DATA_DIR/content"
echo "========================================"
else
echo "$(date +'%Y-%m-%d %H:%M:%S') - 未检测到触发标记文件,无需构建。"
fi
PS:
hugo构建命令;--destination指定输出目录; --source指定工作目录;--theme指定主题;
私有仓库需要申请密钥,才能clone
定时执行构建脚本:
crontab -l #查看定时任务列表
crontab -e #编辑定时任务列表
* * * * * /root/data/docker_data/hugo/hugo-build.sh > /root/data/docker_data/hugo/hugo-build.log 2>&1
PS:一分钟执行hugo-build.sh脚本,输出日志到hugo-build.log
hugo配置文件config.yaml:
安装工程目录要求,需要配置文件才能支持构建。配置文件放在hugo工程根目录
baseURL: https://hugo.eehaiou.com
languageCode: zh-Hans
theme: moments
pagination:
path: .
pagerSize: 10
params:
title: "Hugo Moments"
signature: "人生如逆旅,我亦是行人。"
cover: "demo-background.jpg"
name: "Me"
avatar: "default-avatar.png"

2.3 测试构建脚本
step1: 当我们下载好主题+写好hugo配置文件+写好构建脚本 =》运行编排条件完成;
step2: 我们在手动在webhook/trigger目录下touch一个标志文件build.flag;
step3: 手动运行构建脚本hugo-build.sh;
注意:构建成功条件是:根目录config.yaml + theme主题 + 修复主题源码错误 后面提及
如果你仓库有md文件,执行构建那就显示如下:


PS:如果显示上图,表示仓库拉取成功,hugo构建正常。
3. 配置webhook
webhook的配置,参考docker说明中github仓库的使用说明。readme文档中给出运行脚本参考与post请求断点格式参考。
镜像选择:

配置webhook:

webhook/trigger:是输出构建标志文件目录
webhook/hooks/hooks.json
[
{
"id": "hugo-trigger",
"execute-command": "/scripts/scripts.sh",
"command-working-directory": "/scripts",
"response-message": "已触发执行脚本",
"trigger-rule": {
"match": {
"type": "payload-hash-sha1",
"secret": "my-webhook-hugo-secret-XXXX",
"parameter": {
"source": "header",
"name": "X-Hub-Signature"
}
}
}
}
]
webhook/scripts/scripts.sh:
webhook脚本仅仅创建构建标志文件。
#!/bin/sh
# 脚本功能:创建build.flag触发文件
# 定义触发文件路径
FLAG_FILE="/trigger/build.flag"
# 第一步:检查/trigger目录是否存在,不存在则创建
if [ ! -d "/trigger" ]; then
echo "日志:/trigger目录不存在,先创建..."
mkdir -p "/trigger"
fi
# 第二步:创建/更新build.flag文件
echo "日志:开始创建触发文件${FLAG_FILE}..."
touch "${FLAG_FILE}" # ${}解析
# 第三步:验证文件是否创建成功
if [ -f "${FLAG_FILE}" ]; then
echo "日志:触发文件创建成功!路径:${FLAG_FILE}"
else
echo "错误:触发文件创建失败!"
exit 1 # 执行失败返回非0状态码
fi
调试:
手动commit一个提交,进入webhook容器,查看日志:

结果:如果webhook触发生成了构建标志文件,webhook环节已经调试成功,就可以开启crontab定时任务了
4. NPM反代
反代webhook:
自定义域名指向 hugo-webhook:9000
反代nginx:
自定义域名指向 hugo-nginx:80
nginx映射要求:
nginx容器server 文件的路径要指定到hugo工程public目录。
./hugo-data/public:/usr/share/nginx/html:ro
./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
hugo-nginx容器的nginx站点配置文件:
server {
listen 80;
server_name _;
# 静态页根目录:指向Hugo生成的public目录
root /usr/share/nginx/html;
# 默认首页文件(Hugo生成的静态页首页是index.html)
index index.html index.htm;
charset utf-8;
location / {
# 优先找实际文件,找不到就指向index.html(适配Hugo的静态路由)
try_files $uri $uri/ /index.html;
# 关闭缓存(开发阶段)
add_header Cache-Control "no-cache";
}
location ~* .(css|js|jpg|png|gif|ico|svg|woff|woff2|ttf)$ {
# 缓存有效期1天
expires 1d;
# 开启浏览器缓存
add_header Cache-Control "public, max-age=86400";
# 关闭日志(减少Nginx日志量)
access_log off;
}
location ~ /. {
deny all;
access_log off;
log_not_found off;
}
# ========== 错误页配置 ==========
error_page 404 /index.html;
# 500/502/503/504错误提示
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
结果:访问域名能访问到hugo-data/public/index.html文件的托管服务
5. 修复主题源码错误
moments主题对hugo兼容性修改:moments/layouts/partials/head.html

其他:
1.主题还有不完善的地方,比如图片完全是按图片比例缩小的,没有按照矩形比例缩小,这个可以完善。
2.主题尾页可以取消跳转链接。
6. 最后
以上全部设置正确后我们能够通过域名访问到moments主页了,文章来自于git仓库中的md文件,呈现是经过构建渲染后的html文件。

文章格式要求:moments主题要求的格式:
---
name: Haiou
signature: '@Moments'
avatar: https://imgs3m.eehaiou.com/2026/02/23/699b7648add52.webp
date: 2026-02-23T21:01:20+08:00
pictures:
- https://imgs3m.eehaiou.com/2026/02/23/699b84f2457dd.webp
note: 图片不错吧
top: 1
---
21:01,20:24,19:12,0000测试top,测试note

其实还可以做得更好:
我们接下来使用手机编写以下文章(图片使用私有图床服务生成的链接),推送到仓库,github actions 会执行流水线运行python批处理脚本,把以下文章转换为带元信息的完整格式的文章,转换后的文章自动推送到另外一个仓库。仓库会触发webhook进行静态博客构建。
21:01,20:24,19:12,0000测试top,测试note
https://imgs3m.eehaiou.com/2026/02/23/699b84f2457dd.webp
note: 图片不错吧
top: 1
下一篇我介绍如何完成自动转换文章的,让我们发短博文简单明了,不用手动编写文章的繁琐元信息头部,就让一切自动化完成。
文章评论