缘起
你认识 云游君 吗?
数年前,兴趣使然地,我在云游的一篇教程的指导下成功搭建起了我的第一个博客网站。不过很快我便将其抛之脑后,现在连当时的域名都想不起来了。
两年半前,云游开始开发并宣传他的 Valaxy 框架。两周前,我开始考虑重新搭建我的个人博客;两天前,我开始部署我的博客。
可惜的是,当初云游的那篇入门级教程中只讲了如何白嫖 GitHub Pages, 并未涉及如何将静态文件部署到个人服务器上;而且随着博客框架的改变,当初的教程已经不再适合新的博客。因此,我在这里重新记录下我的部署过程。
当然,作为个人记录,我会略过很多基础但繁琐的内容;我假设读者能够自己搞定我文中未提到的所有步骤,如有不足望海涵。
搭建博客
这里就不细讲了,Valaxy 的文档写得还是挺详细的,照着做没问题:快速上手
记得对默认配置进行个性化的修改。如果对我的修改感兴趣,可以把我的仓库克隆到本地参考:
部署博客
总体思路
部署方案为 GitHub Pages 与个人服务器双部署。GitHub Pages 使用 Valaxy 提供好的工作流文件即可。个人服务器上,我搭建了一个远程 git 仓库,并使用 git hooks ,收到本地推送后,将代码再克隆到服务器上的构建目录;再执行构建命令,最后将构建出的文件复制到 nginx 工作目录,最终自动部署。
准备工作
远程仓库
见我的另一篇博文 搭建自己的 git 远程仓库服务
最终搭建出的博客远程仓库地址为 /home/git/repos/liyuier.github.io.git
构建目录
/usr/dev/blog/liyuier.github.io
. 实际上就是在服务器上的 “本地仓库”
nginx 工作目录
/usr/share/nginx/html
. 用于保存构建后的文件
权限赋予
git hook 脚本由 git
用户执行,而脚本需要有权限修改 nginx 的工作目录。为此需要进行权限修改。
默认情况下,nginx 由 www-data
组下的 www-data
用户执行工作进程。为了实现我们的目标,我们需要将 nginx 工作目录所属的用户组改为 www-data
,然后赋予组内成员对该目录的全读写执行权限,最后将 git
用户加入 www-data
组内。
# 1. 将目录组改为 www-data
sudo chgrp -R www-data /usr/share/nginx/html
# 2. 赋予 www-data 组读写执行权限
sudo chmod -R g+rwx /usr/share/nginx/html
# 3. 将 git 用户加入 www-data 组
sudo usermod -aG www-data git
开始工作
hook 脚本编写
进入远程仓库的 hooks
目录下,即 /home/git/repos/liyuier.github.io.git/hooks
目录
创建名为 post-receive
的文件,并加入以下内容:
# 定义日志文件路径(推荐存放在 git 用户有权限的目录)
LOG_FILE="/home/git/logs/blog_deploy.log"
DEPLOY_LOG_DIR=$(dirname "$LOG_FILE")
# 创建日志目录(如果不存在)
mkdir -p "$DEPLOY_LOG_DIR"
chown git:git "$DEPLOY_LOG_DIR" # 确保目录所有权
# 重定向所有输出到日志文件(追加模式)
exec >> "$LOG_FILE" 2>&1
# 添加时间戳分隔符
echo -e "\n\n=== $(date '+%Y-%m-%d %H:%M:%S') 开始部署 ==="
#!/bin/bash # 指定使用 bash shell 解释器
# 定义变量:工作目录路径(必须与之前创建的目录一致)
TARGET="/usr/dev/blog/liyuier.github.io"
# 定义变量:Nginx 的网站根目录(最终部署位置)
DEPLOY_DIR="/usr/share/nginx/html"
# 定义变量:Git 裸仓库路径(用于 git 命令操作)
GIT_DIR="/home/git/repos/liyuier.github.io.git"
echo "➜ Starting auto deployment..." # 输出开始提示信息
# 强制检出代码到工作目录(--work-tree 指定工作区,--git-dir 指定仓库位置)
git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f
# 切换到工作目录(准备执行构建命令)
cd $TARGET
# 安装依赖(--frozen-lockfile 表示严格使用 lock 文件版本,避免版本冲突)
pnpm install --frozen-lockfile
# 执行构建命令(根据项目配置,生成 dist/ 目录)
pnpm run build
# 清理旧的部署文件(避免残留文件干扰)
sudo rm -rf $DEPLOY_DIR/*
# 递归复制构建结果到 Nginx 目录(-a 参数保留文件属性)
sudo cp -a dist/* $DEPLOY_DIR/
# 修改部署目录所有权(假设 Nginx 以 www-data 用户运行)
# sudo chown -R root $DEPLOY_DIR
echo "✅ Deployment completed! Nginx content updated." # 输出完成提示
# 检查配置语法
if ! sudo nginx -t; then
echo "❌ Nginx 配置测试失败,拒绝重载" >&2
exit 1
fi
# 执行重载
sudo nginx -s reload
echo "🔄 Nginx 已平滑重载"
最终实践
这里为了
减少我的工作量文章简洁,先假设读者已经在服务器上准备好了部署环境,具体来说就是搭设起 nginx 服务器;准备好了博客的构建环境(pnpm)
在本地 PC 上,添加一篇博文,然后提交
git add .
git commit -m "你对本次提交的描述"
由于采用 GitHub Pages 与个人服务器双部署,为了减少心神损耗,建议写个简单的脚本实现这一步。用 Windows 开发就写个 deploy.bat
,用 Linux 就写个 deploy.sh
# 这里假设已经分别为本地仓库绑定了 GitHub 仓和个人远程仓
# 查看远程仓库绑定情况
# git remote -v
# GitHub 仓
# origin git@github.com:liyuier/liyuier.github.io.git (fetch)
# origin git@github.com:liyuier/liyuier.github.io.git (push)
# 个人远程仓
# yuier git@yuier.com:/home/git/repos/liyuier.github.io.git (fetch)
# yuier git@yuier.com:/home/git/repos/liyuier.github.io.git (push)
# 反正博客只有自己一个人开发,直接 push -f
# 具体推送的分支也可以自定义,并相应处理 GitHub 工作流文件与上面的 git hook 脚本
# 我这里懒得折腾,直接推主分支,反正就一个自己用的博客
git push -f origin master
git push -f yuier master
执行脚本后,推送到 GitHub 仓会比较快;而推送到个人仓,由于需要执行 git hook 脚本进行构建与部署,会稍微卡一会,耐心等待就好。
等全部执行完毕后,就可以在浏览器里输入 <你的 nginx 服务地址>
访问网站了。如果部署有问题,就可以去 git hook 中配置的日志文件 /home/git/logs/blog_deploy.log
中查看详情。

附录
关于 nginx 服务的搭建,其实可讲可不讲,因为网上的相关文章太多了,写得好的也很多,我无意再添无足轻重的一笔。这里只挑一些别人写得少的东西记录下来。
域名解析、服务备案
一般的云服务商都会对这两块提供详细的引导,跟着做就行了。我用的是腾讯云,个人感觉除了备案本身的繁琐外没遇到什么牙白的事情。
SSL 证书的申请与配置
申请同样在腾讯云上一站式达成。腾讯云现在(2025 年 3 月)可以免费申请 50 个证书,每个证书有效期 90 天,够用很久很久的了。
申请完后,可以参考 证书安装 这个地方在 nginx 服务下配置。
最后贴一下我的 nginx.conf
#运行用户,默认就是 www-data 组下的 www-data
user www-data www-data;
#启动进程,通常设置成和cpu的数量相等
worker_processes 2;
#全局错误日志
error_log /usr/share/nginx/logs/error.log;
error_log /usr/share/nginx/logs/notice.log notice;
error_log /usr/share/nginx/logs/info.log info;
#PID文件,记录当前启动的nginx的进程ID
pid /run/nginx.pid;
#工作模式及连接数上限
events {
worker_connections 1024; #单个后台worker process进程的最大并发链接数
}
#设定http服务器,利用它的反向代理功能提供负载均衡支持
http {
# 这一坨来自腾讯云推荐配置
# 新增 HTTPS 优化配置
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3; # 禁用旧版协议
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
# 全局开启文件指纹验证
etag on;
#设定mime类型(邮件支持类型),类型由mime.types文件定义
include /usr/share/nginx/conf/mime.types;
default_type application/octet-stream;
#设定日志
log_format main '[$remote_addr] - [$remote_user] [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /usr/share/nginx/logs/access.log main;
rewrite_log on;
#sendfile 指令指定 nginx 是否调用 sendfile 函数(zero copy 方式)来输出文件,对于普通应用,
#必须设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为 off,以平衡磁盘与网络I/O处理速度,降低系统的uptime.
sendfile on;
#tcp_nopush on;
#连接超时时间
keepalive_timeout 120;
tcp_nodelay on;
#gzip压缩开关
#gzip on;
#设定实际的服务器列表
#upstream zp_server1{
# server 127.0.0.1:8089;
#}
# 强制 HTTP 跳转 HTTPS(重要!)
server {
listen 80;
server_name yuier.com;
# 301 永久重定向
return 301 https://$host$request_uri;
}
#HTTPS服务器
server {
listen 443 ssl http2; # 启用 HTTP/2
server_name yuier.com;
# SSL 证书配置(请修改为你的实际路径)
ssl_certificate /usr/share/nginx/ssl/yuier.com_bundle.crt; # 证书链文件
ssl_certificate_key /usr/share/nginx/ssl/yuier.com.key; # 私钥文件
# 启用 OCSP Stapling 提升性能
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;
# 安全响应头
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
index index.html;
location / {
# content location
# root /app;
root /usr/share/nginx/html;
# exact matches -> reverse clean urls -> folders -> not found
try_files $uri $uri.html $uri/ =404;
# non existent pages
error_page 404 /404.html;
# a folder without index.html raises 403 in this setup
error_page 403 /404.html;
# adjust caching headers
# files in the assets folder have hashes filenames
location ~* ^/assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}
}
上面提到了 mime.types
文件,简单说就是配置对于不同文件类型的请求的响应。下面是我的该文件内容:
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
image/svg+xml svg svgz;
image/webp webp;
application/font-woff woff;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.wap.wmlc wmlc;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}