Kylaan

Back

Web 编辑器 Docker 部署#

实现方案: VPS 自托管 Hono 后端 + Vditor 前端, 走 clone-push 流程, Docker 容器化, 反代到 editor.kylaan.cn 目标读者: Kylaan, 跟着敲就上线 撰写时间: 2026-05-23


你部署完之后会拥有#

  • 子域 https://editor.kylaan.cn (HTTPS + Basic Auth 保护)
  • 一个 Docker 容器 blog-editor, 24/7 跑着, 自动重启
  • 浏览器开页 → 写文章 → 一键发布 → 1-2 分钟后 https://kylaan.cn 看到新文章
  • 一份 VPS 上的 git 工作区 (容器内), 跟 GitHub 双向同步
  • 草稿暂存 + 已发文章列表
  • 移动端可用 (Vditor 自带响应式)

时长预估#

Phase内容时间
0DNS + 服务器准备10 分钟
1生成 VPS push SSH key5 分钟
2宝塔加站 + SSL10 分钟
3写项目目录 + 配置文件20 分钟 (复制粘贴)
4nginx 反代 + Basic Auth10 分钟
5启动 + 验证10 分钟
总计~65 分钟

数据流回顾#


Phase 0: 前置准备#

0.1 添加 DNS A 记录#

去你的域名 DNS 控制台 (阿里云/Cloudflare/腾讯云/…) 添加:

记录类型主机名
Aeditor<你的服务器公网 IP>

主机名 = editor, 即完整域名 editor.kylaan.cn

等 1-5 分钟后验证:

# Windows
nslookup editor.kylaan.cn
# 期望 Address 是你的服务器 IP
powershell

DNS 没生效就不要往下做, 否则 SSL 申请会失败。

0.2 确认 Docker 已装好#

SSH 进服务器:

ssh root@<服务器IP>
powershell

检查 Docker:

docker --version
docker compose version
bash
  • 都返回版本号 → 已装好, 继续
  • 提示 command not found → 在宝塔面板里: 软件商店 → Docker 管理器 → 安装, 选最新稳定版

Phase 1: 生成 VPS 用的 GitHub push SSH key#

这是新的一对 key (我们叫 Key ⑤), 跟之前 Actions 用的 Key ② 完全独立。 Key ② 让 Actions runner 进你服务器, Key ⑤ 让你服务器 push 到 GitHub。职责不同, 必须分开

1.1 在服务器上生成 key#

# 还在 root SSH 会话里
mkdir -p ~/.ssh && chmod 700 ~/.ssh

ssh-keygen -t ed25519 -C "blog-editor-vps-push" -f ~/.ssh/blog_webapp -N ""
# -N "" = passphrase 空; 容器内不能交互输入密码
bash

生成两个文件:

  • ~/.ssh/blog_webapp — 私钥, 等下会被容器以只读方式挂载使用
  • ~/.ssh/blog_webapp.pub — 公钥, 等下贴 GitHub
chmod 600 ~/.ssh/blog_webapp
chmod 644 ~/.ssh/blog_webapp.pub
bash

1.2 贴公钥到 GitHub#

打印公钥内容:

cat ~/.ssh/blog_webapp.pub
# 输出形如: ssh-ed25519 AAAAC3...xyz blog-editor-vps-push
bash

整行复制 (从 ssh-ed25519 到末尾的注释), 然后:

  1. 浏览器打开 https://github.com/settings/keys
  2. New SSH key
  3. Title: blog-editor-vps-push
  4. Key type: Authentication Key (不是 Signing Key!)
  5. Key 框里粘贴上面那一整行
  6. Add SSH key

注意: 这是 personal SSH key (绑你 GitHub 账号), 不是 deploy key。因为我们要 push 到私库, deploy key 只能针对单个仓库, personal SSH key 拥有你账号下所有仓库的 push 权限。后续如果想收紧, 可以改成给单仓的 deploy key (本仓库 Settings → Deploy keys → Add deploy key, 勾 “Allow write access”)。

1.3 测连通#

在服务器上:

ssh -i ~/.ssh/blog_webapp -o IdentitiesOnly=yes -T git@github.com
# 期望输出: Hi Kylaan! You've successfully authenticated, but GitHub does not provide shell access.
bash

输错了的话:

报错原因修法
Permission denied (publickey)公钥没贴 / 贴错账号重做 1.2
Host key verification failed第一次连 github.comyes 接受即可
Bad owner or permissionskey 文件权限chmod 600 ~/.ssh/blog_webapp

Phase 2: 宝塔加 editor 子站 + SSL#

2.1 添加站点#

宝塔面板 → 网站 → 添加站点:

字段
域名editor.kylaan.cn
根目录/www/wwwroot/editor.kylaan.cn (默认即可, 反代不会用到这个目录)
PHP 版本纯静态
数据库不创建

提交

2.2 申请 SSL#

刚加完的站点 → 设置 → SSL → Let’s Encrypt:

  • 勾选 editor.kylaan.cn (只勾这一个, 不要勾 www)
  • 申请

等 30 秒, 提示证书已下发。然后:

  • 开启 强制 HTTPS

如果申请失败 “DNS validation failed”, 回 Phase 0.1 等 DNS 再生效几分钟。

2.3 生成 Basic Auth 密码文件#

服务器上:

# 装 htpasswd 工具
apt update && apt install -y apache2-utils

# 生成密码文件
htpasswd -c /www/server/nginx/conf/.htpasswd_editor kylaan
# 提示输入密码两次, 用强密码 (字母数字符号 12+ 位)
# 例如: blog-editor-2026!Xy7
bash

保存密码到你自己的密码管理器, 这是访问编辑器要输的密码。

测试:

cat /www/server/nginx/conf/.htpasswd_editor
# 输出形如: kylaan:$apr1$xyz...$ABCDE
bash

Phase 3: 项目目录 + 所有配置文件#

3.1 建目录结构#

mkdir -p /www/docker/blog-editor/{server,frontend,workspace,drafts,ssh}
cd /www/docker/blog-editor
bash

3.2 把 SSH key 复制 (硬链接) 到容器挂载目录#

cp ~/.ssh/blog_webapp ./ssh/blog_webapp
cp ~/.ssh/blog_webapp.pub ./ssh/blog_webapp.pub
chmod 600 ./ssh/blog_webapp
chmod 644 ./ssh/blog_webapp.pub
bash

为什么不直接挂 ~/.ssh? 因为里面可能还有你 root 本人的其它 key, 把整个目录暴露给容器太宽。单独建子目录只挂这把。

3.3 写 docker-compose.yml#

nano /www/docker/blog-editor/docker-compose.yml
bash

内容:

Ctrl+O 保存, Ctrl+X 退出。

3.4 写 Dockerfile#

nano /www/docker/blog-editor/Dockerfile
bash

内容:

3.5 写 entrypoint.sh#

nano /www/docker/blog-editor/entrypoint.sh
bash

内容:

3.6 写 server/package.json#

nano /www/docker/blog-editor/server/package.json
bash

内容:

{
  "name": "blog-editor-server",
  "version": "0.1.0",
  "type": "module",
  "main": "server.mjs",
  "dependencies": {
    "hono": "^4.6.0",
    "@hono/node-server": "^1.13.0"
  }
}
json

3.7 写 server/server.mjs (后端核心)#

nano /www/docker/blog-editor/server/server.mjs
bash

内容:

3.8 写 frontend/index.html (前端)#

nano /www/docker/blog-editor/frontend/index.html
bash

内容:

3.9 检查目录结构#

cd /www/docker/blog-editor
ls -la
bash

期望看到:

docker-compose.yml
Dockerfile
entrypoint.sh
server/
  package.json
  server.mjs
frontend/
  index.html
workspace/    # 空, 容器启动时 clone
drafts/       # 空
ssh/
  blog_webapp
  blog_webapp.pub
plaintext

Phase 4: nginx 反代 + Basic Auth#

4.1 找到 editor 站点的 nginx 配置文件#

ls /www/server/panel/vhost/nginx/
# 看到 editor.kylaan.cn.conf
bash

4.2 编辑反代#

宝塔面板 → 网站 → editor.kylaan.cn → 配置文件, 或者命令行:

nano /www/server/panel/vhost/nginx/editor.kylaan.cn.conf
bash

找到 server { ... } 块 (HTTPS 那个, 监听 443), 在里面 删掉/注释掉 默认的 location / 块, 替换成:

注意: limit_req_zone 一般要写在 http 块, 写在 server 块某些 nginx 版本会报错。如果保存后 nginx 报错, 把那两行 limit_req_zone + limit_req 删掉, 暂时不限流, 等问题不大。

保存。宝塔会自动 nginx -t && nginx -s reload。报错就看顶部 banner。

4.3 验证 nginx 配置 (不启容器先试一下 auth)#

curl -I https://editor.kylaan.cn/
# 期望: HTTP/2 401   ← 因为还没输密码, 但 Basic Auth 已经生效

curl -I -u kylaan:你的密码 https://editor.kylaan.cn/
# 期望: HTTP/2 502   ← auth 过了, 但后端 (容器) 还没起所以 bad gateway
bash

到这一步 nginx 这一层就 OK 了。


Phase 5: 启动!#

5.1 build + 启动容器#

cd /www/docker/blog-editor
docker compose up -d --build
bash

第一次 build 慢 (装 git/ssh/node 包), 大约 1-3 分钟。

5.2 看日志#

docker logs -f blog-editor
bash

期望看到顺序:

[entrypoint] starting blog-editor container...
[entrypoint] cloning git@github.com:Kylaan/blog-asrto.git into /app/workspace...
Cloning into '/app/workspace'...
remote: Enumerating objects: ...
[entrypoint] starting Hono server on :3001
Blog Editor listening on http://0.0.0.0:3001
plaintext

Ctrl+C 退出 log (容器仍在跑)。

5.3 验证容器是否健康#

docker ps
# 看 blog-editor 状态 STATUS = "Up X minutes (healthy)"
# (healthy 标记要等 60 秒, 因为 start_period: 60s)

# 容器内直接打健康检查
docker exec blog-editor wget -qO- http://localhost:3001/api/health
# 期望: {"ok":true,"time":"2026-..."}
bash

5.4 验证通过 nginx 也能到#

curl -u kylaan:你的密码 https://editor.kylaan.cn/api/health
# 期望: {"ok":true,"time":"..."}
bash

5.5 浏览器打开#

访问 https://editor.kylaan.cn, 浏览器弹原生 Basic Auth 对话框, 输 kylaan + 你的密码。

进入后应能看到:

  • 左侧: “已发布” 列表 (你现有的文章们)
  • 中间: 编辑器 + frontmatter 表单 + 发布按钮
  • 上方标题 📝 Blog Editor

Phase 6: 第一次发文测试#

6.1 写一篇测试文章#

  1. + 新建文章
  2. 填:
    • slug: editor-test-1
    • title: 编辑器测试文章
    • description: 从 web 编辑器发的第一篇
    • publishDate: 今天 (已自动填)
    • tags: 测试
  3. 编辑器里随便写点东西, 比如:
    # 测试
    
    这是从 https://editor.kylaan.cn 发出来的第一篇文章。
    
    $E=mc^2$
    plaintext
  4. 🚀 发布

6.2 观察发布全流程#

界面上会显示:

⏳ 发布中... (git push → GitHub Actions → rsync, 总共 1-2 分钟)
plaintext

然后 5-10 秒后变成:

✓ 已 push! 查看 Actions 进度
(stdout 日志)
plaintext

点 “查看 Actions 进度” 打开 GitHub → Actions, 看新触发的 run。

6.3 验证全链路#

  1. GitHub 上有新 commit: 仓库首页能看到 feat: 新文章《编辑器测试文章》 的最新 commit
  2. GitHub Actions 跑起来: Deploy to Server run, 5 step 全绿 (1-2 分钟)
  3. rsync 完成: 服务器上 ls /www/wwwroot/kylaan.cn/blog/ 能看到 editor-test-1
  4. 网站可见: 浏览器开 https://kylaan.cn/blog/editor-test-1, 文章页正常显示

通了 = 全链路工作 ✅


维护 Cheat Sheet#


排错 FAQ#

Q: docker compose up 报 “permission denied” 读 ssh 私钥 A: chmod 600 /www/docker/blog-editor/ssh/blog_webapp, 然后 docker compose down && up -d。SSH 强制要求 600。

Q: 容器一直 restarting A: docker logs blog-editor 看具体报错。最常见: SSH key 不对, 或 GitHub repo URL 写错。

Q: 浏览器开了页面但 “Vditor 未定义” A: CDN 加载失败, 国内访问 jsdelivr 偶尔不通。临时方案: 等几分钟刷新; 长期方案: 把 vditor@3.10.7/dist/index.cssindex.min.js 下载到 frontend/vendor/, HTML 里引用本地路径。

Q: 发布时 stderr 说 “Updates were rejected because the remote contains work that you do not have locally” A: 你本地用 pnpm publish-post 抢先 push 了, VPS 工作区落后。点 🔄 从 GitHub 同步 按钮, 再发布。

Q: 发布时 stderr 说 “Permission denied (publickey)” git push 不上去 A: VPS 这把 push key (Key ⑤) 没装好。重做 Phase 1, 然后 cp ~/.ssh/blog_webapp /www/docker/blog-editor/ssh/

Q: 图片上传 “请先填 slug” A: 上传按钮要求 slug 已经填写 (因为图片要按文章归档)。先把 slug 填上, 再粘贴/上传图片。

Q: 文章发出去了但 https://kylaan.cn 看不到 A: 看 GitHub Actions 那条 run 是否绿。绿了但还看不到 = nginx 缓存或浏览器缓存, 强刷 (Ctrl+Shift+R)。

Q: nginx 报 unknown directive limit_req_zone A: 把 4.2 配置里 limit_req_zonelimit_req 那两段删掉。或者去 nginx 主配置 nginx.confhttp {} 块里加 limit_req_zone $binary_remote_addr zone=editor_login:10m rate=5r/s;

Q: 想用 root 之外的非特权用户跑容器 A: 在 docker-compose.ymluser: "1000:1000", 然后把 ssh/, workspace/, drafts/chown -R 1000:1000。本指南为了简单全用 root。

Q: 移动端访问能用吗? A: 能, Vditor 自带响应式。但移动端写长文体验一般, 推荐桌面写主要内容, 手机改小段。


后续可扩展功能 (留给你以后做)#

按优先级排:

  1. 文章预览模式: 调 Astro dev server 实时渲染当前文章, iframe 嵌入。需要在容器里多跑个 astro dev 实例
  2. 草稿列表: 当前只有”存草稿”, 没有”列出所有草稿”的 UI。加 GET /api/drafts + 前端 sidebar 多一个 tab
  3. 版本回滚: 一个文件改坏了, 点 “回滚到上一版” → git log <file> + git checkout <hash> -- <file>
  4. 图床外接: 默认图片进 git 历史会让 repo 膨胀。改成上传到 Cloudflare R2 / 七牛 / 阿里 OSS, 只写 URL 进 mdx
  5. 多人协作: 当前 nginx Basic Auth 一个密码所有人共用。改成 JWT + 用户系统, 不同人不同权限
  6. AI 辅助: 集成 Claude API, 给”扩写""润色""生成摘要”按钮
  7. 统计: 哪天写得最多、字数趋势、tags 分布
  8. Markdown 表格 / Mermaid 模板插入: 工具栏加按钮, 一键插入常用代码块

相关文件#


更新这份文档时: 改了 server.mjs / index.html 后, 把这里嵌入的代码块也同步更新, 保持 doc 和实际部署一致。

Web 编辑器
https://kylaan.cn/blog/web_editor_deploy
Author Kylaan
Published at 2026年5月23日
Comment seems to stuck. Try to refresh?✨