GitBook命令行本地部署:将Markdown文档编译为静态网站

GitBook命令行本地部署:将Markdown文档编译为静态网站
先说我这几天的实际经历。团队里积压了二十多份流程文档散落在不同地方的 Markdown 文件里既有新人手册又有接口说明。我本来是想找个在线文档平台统一管理但内容大多还是 Markdown 形态改造成本不小。转了一圈最后用 GitBook 命令行工具做本地部署把整套文档编译成静态站点再通过外部访问方式发布到一个服务器上。这条路走通之后我觉得非常值得写出来从确定用 GitBook 命令行工具、搭建本地编译环境、初始化项目结构到最终让外网真正访问到页面每一步都有不少容易忽略的细节。如果你正在做团队知识库、个人技术博客或者产品文档站并且习惯用 Git 管理 Markdown 源文件那这篇文章的思路大概率能帮你省下很多对比工具的时间。下面我就从选择理由、环境搭建、项目配置、外部访问部署这四个大环节逐步展开最后把我踩过的坑也一并说出来。内容不会太长篇大论讲空道理基本都是可以直接复制改用的命令和配置。1. 为什么我把 GitBook 命令行工具塞进本地文档工作流1.1 GitBook 的典型用法和它在本地发挥的作用很多人对 GitBook 的印象还停留在“一个在线的文档编辑平台”。实际上 GitBook 命令行工具是把同一套 Markdown 文档组织成“一本书”的编译工具它在本地做两件事解析 Markdown 文件然后生成一份可直接通过浏览器浏览的静态网站。整个过程不需要在线编辑器只需要在本地安装命令行工具然后写好目录文件、内容文件和配置文件。对你我这种长期在终端里工作的人来说这样一个命令行工具的优势在于文档源文件就是普通文本文件可以正常进入 Git 仓库、参与 Code Review、用熟悉的各种编辑器来写。而 GitBook 编译出来的静态产物是一堆 HTML、CSS、JS把它放到任何能托管静态资源的服务器上即可实现外部访问。这个特点让“本地部署 外部访问”成为非常自然的组合。需要先澄清一个概念GitBook 命令行工具本身并不依赖必须装在某台开发机上也可以在服务器上执行。但更舒服的工作流是在本地编写、本地编译预览再把最终的静态产物推送到有公网入口的服务器。这样开发机可以随时关机文档访问却不受影响。1.2 什么场景最适合走“本地命令行 外部发布”这条路先说最适合的人群。第一类是技术团队内部文档维护者团队里已经有 Git 仓库习惯文档也在仓库里不希望把内容再从 Git 里搬到另一个在线系统这时候用 GitBook 命令行工具可以把库里已有的 Markdown 直接变成网站。第二类是给开源项目或产品写文档的个人开发者本地习惯用编辑器写发布时希望能一条命令完成。第三类则是想把文档彻底私有化不把内容放到第三方文档平台的人。不适合这套方案的场景也有。如果团队里多数成员是非技术同事特别依赖可视化编辑和多人协同在线编辑那 GitBook 命令行工具的纯本地工作流会让他们犯难。因为每一个成员都需要自己熟悉 Markdown 和 Git 提交这种门槛并不是每个人都愿意跨过。1.3 本地环境和外部环境的职责划分用一句话概括我的设计思路本地环境负责“写和编译”外部环境只负责“托管和访问”。本地目录里管理 README.md、SUMMARY.md、book.json 这类源文件编译后生成_book目录这个目录中所有文件都是最终给读者看的内容。在外部访问这一层最稳妥的方法不是把开发机常开着并开放端口而是准备一台有公网能力的云服务器把_book内容推送上去然后用 Nginx 这类软件把目录暴露成站点。开发者提交文档更新后只需要重新编译并推送增量文件外部访问并不会受本地环境影响。这个思路能让文档维护不依赖“某个人那台电脑是不是关机了”这种不可控因素。2. 前置环境与安装版本选型决定后面八成是否顺利2.1 Node.js 环境准备与版本选择GitBook 命令行工具本质上是基于 Node.js 的命令行程序。在正式安装之前你电脑里需要先有 Node.js 和 npm。这一步看似基础但版本选择非常关键。我实际测试下来GitBook CLI 不是“越新越稳”它比较依赖旧版 Node 环境。如果你用的是长期维护的 Linux 服务器或 Mac建议先用 nvm 管理 Node 版本。安装 nvm 的命令如下curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc然后安装并使用 Node 16。为什么推荐 16因为 GitBook CLI 的底层编译链路还保留着老式 Node 项目的影子它在 Node 17 以上的新版本环境中偶尔会发生哈希算法相关的报错。Node 16 是我实测下来最省心的版本。nvm install 16 nvm use 16 node -v如果你已经在使用更高版本的 Node并不代表完全不能用只是需要额外设置环境变量来兼容。具体报错和解决办法我会在后文单独说。如果只是临时评估这个方案也可以直接用nvm use 16切换版本不影响你电脑上其它项目。2.2 安装 gitbook-cli 并验证命令可用Node 环境就绪后安装命令行工具只需要一条命令npm install -g gitbook-cli2.3.2指定版本号是我特意加上的。如果你直接执行npm install -g gitbook-cli大概率会安装到 2.3.2 这个版本但如果未来 npm 上有其它变动锁版本号能避免一些隐藏破坏。安装完成后通过下面的命令确认gitbook -V正常输出类似CLI version: 2.3.2 GitBook version: 3.2.3第一行表示命令行工具的版本第二行表示 GitBook 运行时版本。看到这两行输出即代表安装已经成功。这一步还要提醒一个小问题GitBook CLI 首次运行某些命令时会自动下载它需要的 GitBook 运行时版本。如果安装过程过慢或者卡住不动通常是因为网络连接到 npm 仓库较慢可以检查你的 npm 源配置如果是在内网离线环境则需要先把相关 npm 包下载好再手动传入。2.3 如果 Node 版本过高导致 GitBook 启动失败这是我实际踩过的一个坑。环境原本是 Node 20运行gitbook serve时直接报出类似下面的错误Error: error:0308010C:digital envelope routines::unsupported错误原因比较底层是 Node 17 以后 OpenSSL 的策略发生了变更而 GitBook 内部使用的旧依赖仍然按照旧策略在运行。解决办法有两种我推荐第一种直接切换到 Node 16 环境再执行命令。第二种则是在当前高版本 Node 下临时启用兼容模式export NODE_OPTIONS--openssl-legacy-provider这个环境变量会告诉 Node 使用旧的 OpenSSL 提供方设置之后重新执行gitbook serve。这个方法我只建议用来临时跑通不要长期依赖因为高版本 Node 下还可能遇到其它兼容问题一味打补丁会消耗很多精力。真正稳定的做法还是把 Node 版本固定到本次使用的命令能接受的范围内。3. 从空目录到可浏览站点初始化、配置和本地预览3.1 gitbook init 生成的骨架目录结构安装完成后打开终端进入一个你想放置文档的目录。我第一次操作时用的是这样一个场景mkdir team-docs cd team-docs gitbook init执行gitbook init后GitBook 会自动创建书籍所需的基础文件。最核心的两个文件是 README.md 和 SUMMARY.md。前者是文档首页内容相当于整本书的欢迎页后者是整个文档的目录结构GitBook 会靠它决定侧边导航栏怎么展示。创建出来的初始目录大致如下team-docs/ ├── README.md └── SUMMARY.md如果这时候直接运行gitbook serve浏览器打开http://localhost:4000你就能看到一本只有首页和简单目录的电子书。不过要想让文档真正可用需要先理解 SUMMARY.md 在 GitBook 命令行工具眼中的地位它不只是一个普通 Markdown 文件而是整本书的“导航配置”。所有页面都必须被它引用读者才能真正从导航里进入对应页面。3.2 SUMMARY.md本地书目录的核心文件SUMMARY.md 的基本语法是 Markdown 列表。每一行对应一个页面层级关系靠缩进控制。我通常会这样维护 SUMMARY.md# 目录 * [项目介绍](README.md) * [部署手册](deploy/README.md) * [环境要求](deploy/env.md) * [安装步骤](deploy/install.md) * [常见问题](deploy/troubleshooting.md) * [接口文档](api/README.md) * [用户接口](api/user.md) * [订单接口](api/order.md)这里有一个非常容易忽略的细节GitBook 不区分 URL 文件名也不要求章节文件名必须按照数字编号。它完全按照 SUMMARY.md 里的顺序展示导航所以你需要自己去控制文档逻辑顺序。如果某一天你想调整章节顺序修改 SUMMARY.md 里的行序即可不需要重命名文件。这种设计虽然让命令行的自由度更高但也要求你一开始就把目录结构设计得合理。另外每一章最好都放一个 README.md即使这一章下面的子页面好几个。章节 README 会作为点击章节标题时默认打开的页面。在 GitBook 生成的侧边栏里章节名称和其下子页面会形成可折叠的树状结构查看起来比较接近一本正式的线上书。3.3 通过 book.json 调整标题、语言、插件和资源路径如果只用默认配置GitBook 生成站点时书名会直接用目录名章节排序来自 SUMMARY.md。想要进一步控制页面标题、语言、描述等信息就需要在项目根目录创建 book.json。下面是我常用的一个配置模板{ title: 团队技术文档中心, description: 存放运维部署、开发规范与接口文档, language: zh-hans, gitbook: 3.2.3, root: ., links: { sidebar: { 首页: https://example.com } } }关键的字段含义如下配置项作用title整本书的标题会显示在浏览器标签和页面顶部description站点描述信息对 SEO 和分享链接更友好language语言代码中文建议写成zh-hansgitbook锁定 GitBook 运行时的版本避免升级产生波动root指定文档根目录默认是项目根目录links.sidebar在侧边栏里增加自定义外链当你在 SUMMARY.md 里加了自定义图片存放目录或静态资源时路径最好以相对 Markdown 文件所在目录的方式去写因为 GitBook 编译时会重新组织页面和资源之间的对应关系。过于依赖绝对网站路径会让本地预览和外部部署产生不一致。book.json 还有一个重要用途是插件管理。GitBook 命令行工具支持通过插件来扩展语法高亮、目录折叠、代码块增强等能力。比如要启用一个插件的写法是先在 book.json 的plugins字段里写入插件名然后在项目目录执行gitbook install它会自动从 npm 仓库把插件下载到项目本地。如果你不需要某个默认插件可以在插件名前加-前缀来禁用它。这个机制非常灵活但带来的问题也是插件版本和 Node 版本必须匹配所以每次调整插件后都要重新编译测试一遍。3.4 本地编译与预览的使用逻辑GitBook 命令行工具有两个常用命令gitbook serve和gitbook build。gitbook serve会启动一个本地开发服务器默认监听地址是http://localhost:4000。它适用于本地写作场景当你修改了 Markdown 文件后页面会自动刷新非常方便。它的工作方式并不是把文件原封不动地映射成页面而是会先把内容编译到内存再以网站形式提供浏览。gitbook build只做编译不会启动本地服务器。编译后的产物默认生成在项目根目录的_book文件夹。打开这个目录你会看到 index.html 以及一堆静态资源文件。这些文件才是可以发布到外部访问的真正产物。所以完整的本地迭代流程通常是先用gitbook serve边写边预览反复调整内容和目录确认无误后执行gitbook build再把_book里的内容上传到外部服务器。这两个命令各有各的用途不要混用。有人问过我为什么不直接在生产服务器上放完整项目目录还要单独编译。原因是外部访问服务器只需要给读者看静态页面不需要把源码、配置、甚至 Git 历史都暴露出去。_book目录正好是经过编译的干净产物。在实际使用中我建议把_book目录加入.gitignore避免把编译产物提交到源码仓库。GitBook 很早就推荐将源文件和生成产物分开管理。这样做的好处是你的 Git 仓库保持简洁每次比较变更都是 Markdown 上的文字差异而不是一堆易混淆的压缩脚本差异。4. 外部访问给你的文档找到正确的外网入口4.1 先明确外部访问不等于把开发机端口直接裸奔文档编译完成后最本能的思路是把本地服务端口开放出去让人访问。这个思路在某种小范围测试场景下可以成立你启动gitbook serve后让同一局域网内的同事通过你的局域网 IP 访问页面。但真正做对外发布时我并不推荐直接把自己的开发机当成长期 Web 服务器原因主要有三个。开发机通常不在固定的机房网络环境里IP 可能频繁变化无法稳定对应一个域名或地址。第二文档站点作为对外服务需要一个相对持续稳定的进程管理方式而开发机往往没有独立的进程守护和服务监控。第三也是最重要的是如果只是图省事把端口直接暴露出来几乎等于把一堆未经过滤的入口敞开在公网环境中风险远大于收益。更好走的路是把整个部署流程拆成两个环节先在本地执行gitbook build生成静态目录再把_book放到一台具有公网服务的服务器环境里由 Nginx 这类标准 Web 服务器来提供访问。这样一来本地机器只负责写作外部流量的稳定性和安全性都由专门的服务器承担。4.2 把 _book 当作最终交付物推送到服务器在本地项目根目录执行gitbook build构建完成后ls _book能看到 index.html 文件。下一步就是把整个_book目录的内容同步到服务器上。我在大多数场景下使用的是 rsync因为它在同步大量文件时能只上传差异部分对后续频繁更新文档非常友好。假设服务器的 IP 是your-server-ip目标路径我们定为/var/www/docs同步命令可以这样写rsync -av --delete ./_book/ rootyour-server-ip:/var/www/docs/这里的-a表示归档模式保留权限、时间戳等文件属性-v让过程可视化--delete表示删除服务器目标目录里有、本地已经没有的文件。最后这个参数很重要如果你删除了一篇 Markdown 文档并重新构建不加上它旧页面会残留在服务器上容易造成读者访问到已下架的内容。首次同步前你需要确保服务器上已经创建了对应目录并且当前用户有权限写入。比较稳妥的服务器目录权限设计是把网站根目录的所有者设置成 Nginx 运行用户通常叫www-data同时让部署账号拥有写入权限。这样既能保证 Nginx 能读取也便于你后续用 rsync 继续推送。4.3 用 Nginx 提供静态站点服务并绑定域名同步完成后外部访问的服务器端还需要一个 Nginx 站点配置。假设你已经将云服务器的安全组或服务入口打开并将域名docs.example.com解析到了这台服务器。Nginx 的站点配置文件可以这样写server { listen 80; server_name docs.example.com; root /var/www/docs; index index.html; location / { try_files $uri $uri/ 404; } }这段配置核心就三件事监听 80 端口、指定网站根目录、当请求到达某个路径时先去磁盘上找对应文件。try_files $uri $uri/ 404的意思是如果路径对应一个文件就直接返回文件内容如果对应一个目录就尝试加载目录里的 index.html如果都没有则返回 404。对于 GitBook 编译出来的静态站点这套配置已经足够因为 GitBook 生成的页面严格遵循普通静态文件规则不涉及服务端动态处理。写完配置后要测试语法并重载服务nginx -t systemctl reload nginx如果你是第一次配置 Nginxnginx -t会帮你检查文件语法是否正确这一步强烈建议不要跳过。配置里任何遗漏的分号、括号错误都会在这里暴露出来直接 reload 可能不会生效。4.4 加上 HTTPS 和访问控制现在的 Web 环境里没有 HTTPS 的站点会让浏览器直接提示不安全特别是如果文档里有登录相关说明或者涉及账号信息用户观感会很差。使用 Certbot 给 Nginx 配置自动证书非常方便sudo certbot --nginx -d docs.example.com这个命令会自动修改 Nginx 配置把 80 端口请求重定向到 HTTPS并填入证书路径。成功后再次访问域名浏览器地址栏就会显示安全连接状态。如果你的文档强调“只能内部访问”还可以在 Nginx 层面增加最基础的身份验证。先生成密码文件sudo apt install -y apache2-utils sudo htpasswd -c /etc/nginx/.htpasswd reader然后把 Nginx 站点配置的 location 区域改成location / { auth_basic Restricted Documentation; auth_basic_user_file /etc/nginx/.htpasswd; try_files $uri $uri/ 404; }这样读者打开页面时浏览器会先弹出一个用户名密码输入框。虽然这不是最精细的权限方案但对大多数内部文档场景来说已经能挡住绝大多数无意中的访客。更重要的是这种方式只作用于文档服务本身没有给本地开发机打开任何额外入口。4.5 不同发布路径的取舍建议除了上面的 Nginx 静态服务器方案实际可选的外部访问路径还不少我把它们放在一起做了一个对比发布路径优点适合场景云服务器 Nginx资源可控、可自定义域名和证书、方便设置访问控制团队正式文档、需要稳定访问的站点对象存储静态托管免运维、支持 CDN、部署简单公开文档、访问量较大的场景Git 仓库 Pages 服务与代码提交天然联动历史版本清晰开源项目文档局域网内访问速度快、数据不出域仅限同网络内的临时预览我最终选择云服务器 Nginx原因在于团队有一批非公开文档需要固定的访问控制。而如果你写的是完全公开的项目文档选择对象存储或者 Pages 服务会更轻量。不要一上来就追求最复杂方案先看自己的目标读者能不能正常访问再决定维护成本高不高。5. 一次真实的发布过程从 gitbook build 到 URL 可访问5.1 本地文档项目状态检查为了让你更直观地理解整套流程我在这里还原一次完整的操作过程。前提是本地已经有一个 GitBook 项目目录结构如下team-docs/ ├── README.md ├── SUMMARY.md ├── book.json ├── .gitignore ├── deploy/ │ ├── README.md │ ├── env.md │ └── install.md └── api/ ├── README.md └── user.md先执行git status确认当前文件状态。如果这是一个 Git 仓库且你还没提交过内容那我建议先提交一次保证在发布前有一个可回滚的稳定版本。命令行工具的好处在这里体现得很明显源文件的每一次变化都被 Git 记录下来任何一次发布出错都能快速对比出本地和服务器之间的差异。5.2 执行构建并核对产物进入项目根目录后执行gitbook build这条命令执行完如果没有任何报错通常会有类似下面的提示info: 7 pages are built in 3.42 seconds info: generation completed看到这个信息后不要着急上传先检查_book目录内容ls _book你需要确认_book/index.html存在并且 SUMMARY.md 中列出的页面文件也都出现在对应路径里。最常见的构建问题是中文路径名和空格文件名因为 GitBook 会把 Markdown 里的链接解析成对应的 HTML 路径如果文件名包含中文生成的链接有时会显示成编码后的字符。这本身不会导致页面无法访问但会增加排查成本。我建议你从一开始就把文档文件名统一成英文小写加连字符。5.3 推送与服务器目录权限处理确认产物没问题后执行 rsync 推送rsync -av --delete ./_book/ rootyour-server-ip:/var/www/docs/如果第一次执行时提示权限不足不要硬改目录权限为 777。正确做法是在服务器上登录 root 用户执行chown -R www-data:www-data /var/www/docs chmod -R 755 /var/www/docswww-data是 Nginx 默认的运行用户把目录所有权交给它后Nginx 才能正常读取文件。同时755 权限让普通用户也能进入目录读取页面但只有文件所有者才能修改内容。这样设计后如果你后续需要用非 root 账号运行 rsync再把部署公钥加入该账号并确保它对/var/www/docs有写入权限即可。5.4 Nginx 测试、HTTPS 申请与最终验证服务器上 Nginx 站点配置和上文保持一致执行配置校验与重载后还要从外部视角做一次访问验证。可以在本地终端执行curl -I https://docs.example.com如果返回HTTP/2 200并且能看到content-type: text/html就说明站点已经通过外部访问正常响应。如果返回 403大概率是目录权限问题返回 404则可能是 Nginx 的 root 路径写错了返回连接超时需要检查服务器安全组以及域名解析是否生效。这一步常被忽略的一个细节是申请 HTTPS 证书前必须保证域名已经解析到服务器并且 80 端口能正常访问因为 Certbot 需要使用 HTTP 协议来完成签发验证。如果域名尚未解析或者解析记录没有生效certbot会直接报错并退出。6. 我踩过的坑和现在仍然坚持的分层习惯6.1 三个高频问题附带成因第一个坑是 Node 版本带来的启动失败这个问题我在前面已经详细说过。它提示我们要把 GitBook CLI 的版本和 Node 版本放在一起当成同一个环境约束来管理不要只关注装上没装上。第二个坑是 SUMMARY.md 中的层级写错。GitBook 对缩进非常敏感如果你希望某个页面成为某一个章节的子页面就必须保证它的列表层级在父项下方并且缩进正确。我见过很多次因为少了一个空格或者多了一个 Tab导致目录侧边栏完全失去嵌套关系。这个问题的规律是看起来文件都在页面也能访问但侧边导航和预期完全不一致。排查时第一步就是检查 SUMMARY.md 的原始文本而不是去改动页面内容。第三个坑是静态资源路径问题。当文档里用了图片、PDF 或附件时相对路径在本地预览可能正常构建后发布到服务器子目录时却会失效。最安全的做法是让文档里的所有资源都使用相对当前页面的路径并保持项目内文件路径结构的统一。如果你必须在多个页面里复用同一张图片建议把图片放在一个公共assets目录里然后每个页面通过相对路径引用。这样无论gitbook serve还是构建后的静态文件路径关系都能保持一致。6.2 文档更新后如何快速重新发布文档维护不会是一次性工作你需要一个可以反复使用的更新流程。我一般只需要四步# 1. 拉取其他人的最新修改 git pull # 2. 修改对应的 Markdown 文件 vim deploy/install.md # 3. 提交并推送 git add -A git commit -m 更新安装说明 git push # 4. 重新编译并同步到服务器 gitbook build rsync -av --delete ./_book/ rootyour-server-ip:/var/www/docs/有人可能觉得每次都要重新执行编译和同步有点麻烦。还有另一种做法是在服务器上设置 Git 仓库通过git pull拉取源文件再在服务器上执行gitbook build。这种方法可以减少本地打包上传过程但要求服务器上也安装相同版本的 Node 和 GitBook。两种方式没有绝对的好坏。我选择本地编译是因为我希望编译这一动作尽可能少地在服务器上发生服务器只承担最简单的文件访问和托管职责。这样当编译报错时问题边界清楚不会牵扯出服务器环境变量、运行时权限等额外因素。6.3 把文档源文件和生成站点分开管理踩了不少坑后我现在养成的最重要习惯就是内容、配置、产物三者彻底分层。内容层就是普通 Markdown 文件按模块放在不同目录配置层是 book.json 和 SUMMARY.md产物层是_book完全由命令行工具生成从不手工修改。Git 仓库只跟踪前两层_book永远不进入版本控制。这个习惯带来一个很直接的收益无论是换一台电脑继续写文档还是新同事接手项目只要把他拉到同一个 Git 仓库并安装好相同版本的 Node 和 GitBook CLI他就能在本地完整复现整本书的编译结果。文档维护不再依赖某个人的电脑环境排障范围也大大缩小。如果你准备开始动手我最后想分享的实操建议是先不要一上来就追求很多花哨的插件和复杂主题用最基础的功能跑通一条最简单的链路也就是本地初始化、目录写好、GitBook 构建、Nginx 访问这个最小闭环。等这个闭环稳定跑过一周再根据实际需求逐步加搜索、代码高亮、访问统计等功能。这样每一项新功能引入时都能快速验证也不会让整套本地部署体系一开始就背上太多不确定性。我后来把团队文档的权限粒度继续细化了一些但基础架构一直没有变。本地写 Markdown执行 GitBook 命令行工具编译再把静态产物同步到外部服务器这条链路简单、可控、出问题也好查。希望这篇文章也能让你少走一些我当初绕过的弯路。

最新新闻

日新闻

周新闻

月新闻