从本地写作到自动上线:VuePress 博客自动部署完整记录
从本地写作到自动上线:VuePress 博客自动部署完整记录
本文记录「AI 测研社」博客从本地写文章,到 GitHub Actions 自动构建并部署至云服务器的完整过程。
完成配置以后,日常更新博客只需要:
本地写文章 → Git 提交并推送 → GitHub Actions 构建 → 自动部署 → 网站更新一、最终实现效果
flowchart LR
A["本地 PyCharm<br/>编写 Markdown"] --> B["Git 提交"]
B --> C["推送到 GitHub<br/>main 分支"]
C --> D["GitHub Actions<br/>安装依赖并构建"]
D --> E["SSH + rsync<br/>上传静态文件"]
E --> F["1Panel / OpenResty<br/>提供网站访问"]
F --> G["ceyanshe.com<br/>展示最新内容"]自动化链路跑通后,不再需要手动执行以下工作:
- 手动上传文章到服务器。
- 手动执行 VuePress 构建。
- 手动复制
dist文件。 - 每次更新都登录 1Panel。
二、整体环境
| 项目 | 当前使用方案 |
|---|---|
| 博客框架 | VuePress + Theme Plume |
| 本地编辑工具 | PyCharm / Obsidian |
| 文章格式 | Markdown |
| 代码托管 | GitHub |
| 自动构建 | GitHub Actions |
| 云服务器管理 | 1Panel |
| Web 服务 | 1Panel OpenResty |
| 域名 | ceyanshe.com |
| Git 主分支 | main |
| VuePress 构建目录 | src/.vuepress/dist/ |
| 服务器部署目录 | /opt/1panel/www/sites/ceyanshe.com/index/ |
| OpenResty 容器内目录 | /www/sites/ceyanshe.com/index/ |
服务器宿主机路径和 OpenResty 容器内路径不同。GitHub Actions 向宿主机目录部署,而 Nginx 配置使用容器内映射路径。
三、项目目录建议
ai-test-blog/
├── .github/
│ └── workflows/
│ └── deploy.yml
├── src/
│ ├── .vuepress/
│ │ ├── config.ts
│ │ ├── plume.config.ts
│ │ └── public/
│ └── posts/
│ ├── ai-testing-practice/
│ └── blog-building/
├── .gitignore
├── package.json
└── package-lock.json文章图片建议按文章单独建目录:
src/notes/
├── vuepress-auto-deploy.md
└── images/
├── 01-github-secrets.png
├── 02-actions-success.png
└── 03-website-online.pngMarkdown 中引用图片时必须使用真实存在的相对路径:
如果图片尚未放入项目,不要提前写图片引用,否则 VuePress/Vite 构建可能报错:
Rolldown failed to resolve import "img.png"四、先确认本地能够构建
第一次配置自动部署前,先在项目根目录安装依赖并执行构建:
npm install
npm run docs:build构建成功后,静态文件通常生成到:
src/.vuepress/dist/如果本地构建失败,应先解决文章路径、Frontmatter、图片引用或主题配置问题,再配置远程部署。
五、服务器准备
1. 确认部署目录
本次使用的服务器部署目录为:
/opt/1panel/www/sites/ceyanshe.com/index/构建文件部署成功后,该目录应直接包含:
index.html
assets/
posts/不要多套一层 dist 目录,否则网站根目录可能找不到 index.html。
2. 安装 rsync
GitHub Actions 使用 rsync 同步静态文件,先在 Ubuntu 服务器检查:
rsync --version如果提示命令不存在:
apt update
apt install -y rsync3. 创建部署专用 SSH 密钥
在服务器执行:
ssh-keygen -t ed25519 -N "" -C "github-actions-deploy" \
-f ~/.ssh/github_actions_deploy生成两个文件:
~/.ssh/github_actions_deploy # 私钥
~/.ssh/github_actions_deploy.pub # 公钥把公钥加入服务器授权列表:
cat ~/.ssh/github_actions_deploy.pub >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys查看私钥:
cat ~/.ssh/github_actions_deploy私钥内容必须从下面的开头复制到结尾:
-----BEGIN OPENSSH PRIVATE KEY-----
中间全部内容
-----END OPENSSH PRIVATE KEY-----私钥只能保存到 GitHub Secrets。不要放进文章、截图、聊天记录或 Git 仓库,也不要复制
.pub公钥作为私钥。
六、配置 GitHub Actions Secrets
进入 GitHub 仓库:
Settings
→ Secrets and variables
→ Actions
→ Repository secrets
→ New repository secret依次创建以下 4 个 Secret:
| Secret 名称 | 填写内容 |
|---|---|
DEPLOY_HOST | 云服务器公网 IP |
DEPLOY_PORT | SSH 端口,例如 22 |
DEPLOY_USER | SSH 用户,例如 root |
DEPLOY_SSH_KEY | 部署私钥的完整内容 |
注意事项:
DEPLOY_PORT只能填写端口数字,不能填写SSH等文字。DEPLOY_SSH_KEY必须保留原始换行。- Secret 保存后无法再次查看,只能更新或删除。
- 不要在工作流文件中直接写 IP、密码或私钥。
七、创建 GitHub Actions 工作流
在项目中新建:
.github/workflows/deploy.yml示例工作流:
name: Deploy VuePress Blog
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Build VuePress
run: npm run docs:build
- name: Prepare SSH
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
mkdir -p ~/.ssh
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p "$DEPLOY_PORT" -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
- name: Deploy to server
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
run: |
rsync -avz --delete \
-e "ssh -i ~/.ssh/deploy_key -p $DEPLOY_PORT" \
src/.vuepress/dist/ \
"$DEPLOY_USER@$DEPLOY_HOST:/opt/1panel/www/sites/ceyanshe.com/index/"其中:
push → main:每次推送到main自动触发。workflow_dispatch:允许在 GitHub Actions 页面手动运行。npm ci:按照package-lock.json安装依赖。npm run docs:build:构建 VuePress 静态文件。rsync --delete:同步新文件,并删除服务器中已经被本地移除的旧文件。
rsync --delete会删除目标目录中源目录不存在的内容。因此目标必须精确填写为 VuePress 的专用部署目录,不能误填网站根目录或其他业务目录。
八、首次提交并推送
先检查本地文件:
git status确认没有提交以下内容:
node_modules/
src/.vuepress/dist/
.idea/
.env
SSH 私钥
API Key
账号密码然后提交:
git add .
git commit -m "feat: 初始化 AI 测试博客并配置自动部署"
git push origin main如果 PyCharm 图形界面一直显示“正在推送”,可以取消后台任务,改在终端执行:
git push origin main成功时会看到类似:
main -> main九、查看 GitHub Actions
推送成功后进入:
GitHub 仓库 → Actions任务状态说明:
| 状态 | 含义 |
|---|---|
| 黄色圆点 | 正在运行 |
| 绿色对勾 | 构建和部署成功 |
| 红色叉号 | 某个步骤执行失败 |
如果失败,点击工作流记录,再展开带红叉的步骤,重点查看日志末尾的报错。
修改 GitHub Secret 后无需重新提交代码,可直接:
Actions
→ 打开失败记录
→ Re-run jobs
→ Re-run failed jobs十、让域名展示 VuePress 静态网站
GitHub Actions 部署成功,只代表文件已经上传。域名还需要由 OpenResty 指向 VuePress 静态目录。
本次最初的 OpenResty 配置仍包含 WordPress 反向代理:
location / {
proxy_pass http://服务器IP:8080;
}因此访问 ceyanshe.com 时,显示的仍是原 WordPress 博客。
修改后的 OpenResty 配置
在 1Panel 中进入:
网站
→ ceyanshe.com
→ 配置文件完整配置如下。核心变化是增加静态目录 root,并用 try_files 替换 WordPress 反向代理:
server {
listen 80;
listen 443 ssl;
server_name ceyanshe.com www.ceyanshe.com;
root /www/sites/ceyanshe.com/index;
index index.html index.htm default.html;
access_log /www/sites/ceyanshe.com/log/access.log main;
error_log /www/sites/ceyanshe.com/log/error.log;
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) {
return 404;
}
location ^~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}
if ($uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$") {
return 403;
}
location / {
try_files $uri $uri/ /index.html;
}
http2 on;
if ($scheme = http) {
return 301 https://$host$request_uri;
}
ssl_certificate /www/sites/ceyanshe.com/ssl/fullchain.pem;
ssl_certificate_key /www/sites/ceyanshe.com/ssl/privkey.pem;
ssl_protocols TLSv1.3 TLSv1.2;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
error_page 497 https://$host$request_uri;
proxy_set_header X-Forwarded-Proto https;
add_header Strict-Transport-Security "max-age=31536000";
}这里使用的是 OpenResty 容器内路径:
/www/sites/ceyanshe.com/index它对应服务器宿主机路径:
/opt/1panel/www/sites/ceyanshe.com/index修改后检查配置:
docker exec 1Panel-openresty-dDgD openresty -t如果出现:
syntax is ok
test is successful再重新加载:
docker exec 1Panel-openresty-dDgD openresty -s reload最后用无痕窗口访问:
https://ceyanshe.com/或者在 Windows 浏览器按 Ctrl + F5 强制刷新。
十一、本次遇到的问题与解决方法
问题 1:文章图片不存在,VuePress 构建失败
报错:
Rolldown failed to resolve import "img.png"原因:
文章引用了不存在的图片。
解决方法:
- 删除不需要的图片引用;或
- 把图片放到正确目录;或
- 修改为正确的相对路径。
例如:
修复后先在本地执行:
npm run docs:build问题 2:SSH 端口填写错误
报错:
Bad port 'SSH'原因:DEPLOY_PORT 填成了文字 SSH。
解决:在 GitHub Repository secrets 中把 DEPLOY_PORT 改为真实数字,例如:
22问题 3:SSH 私钥无法读取
报错:
Load key "/home/runner/.ssh/deploy_key": error in libcrypto常见原因:
- 私钥没有完整复制。
- 原始换行被破坏。
- 误把
.pub公钥填进了 Secret。 - 私钥首尾行缺失。
解决:重新复制无密码 OpenSSH 私钥的完整内容,更新 DEPLOY_SSH_KEY。
问题 4:Actions 成功,但网站仍显示 WordPress
原因:OpenResty 的 location / 仍然通过 proxy_pass 反向代理到 WordPress 的 8080 端口。
解决:
root /www/sites/ceyanshe.com/index;
location / {
try_files $uri $uri/ /index.html;
}保存后检查并重新加载 OpenResty。
问题 5:文章 Frontmatter 显示在正文中
错误写法通常是缺少开头或结尾的 ---。
正确格式:
---
title: 文章标题
date: 2026-07-24
tags:
- VuePress
- 自动部署
categories:
- 博客搭建
---
这里开始写正文。十二、以后发布新文章的固定流程
第一步:创建 Markdown 文章
在对应分类目录中新建 .md 文件,完善 Frontmatter 和正文。
第二步:本地预览或构建
开发预览:
npm run docs:dev正式构建检查:
npm run docs:build第三步:检查 Git 变更
git status第四步:提交并推送
git add .
git commit -m "docs: 发布新的 AI 测试实战文章"
git push origin main第五步:查看自动部署
GitHub → Actions → 等待绿色对勾第六步:检查线上网站
访问:
https://ceyanshe.com/确认:
- 首页是否出现新文章。
- 文章页面能否正常打开。
- 图片是否正常加载。
- 代码块和目录是否正常显示。
- 手机端排版是否正常。
十三、发布前检查清单
总结
本次完成了 VuePress 博客的持续部署链路:
Markdown 内容
→ Git 版本管理
→ GitHub 代码托管
→ GitHub Actions 自动构建
→ SSH/rsync 部署服务器
→ OpenResty 提供 HTTPS 访问整套配置只需要完成一次。以后博客的核心工作会回到内容创作本身:本地写文章、检查构建、提交推送,网站便会自动更新。