在上一篇文章中,已经在本地用 Nginx 跑通了静态站点。然而,http://localhost 只有你自己的电脑能访问。如果想让网站发布到互联网,需要将它部署到 。
什么是生产环境
从本地开发到线上生产,核心变化在于服务对象的改变。我们用一张表直观对比:
| 维度 | (Development) | 生产环境 (Production) |
|---|---|---|
| 服务用户 | 你自己 | 互联网真实用户 |
| 间 | 随用随开 | 7 × 24 小时无间断 |
| 网络边界 | 本地局域网 | 互联网公网可达 |
| 数据故障 | 随时重来 | 线上事故 |
要让网站上线,生产环境必须跨越两个最低门槛:7 × 24 小时运行 与 具备公网 IP。对于个人而言,最快捷、低成本的达成方案就是租用一台 。
为什么选择云服务器
云厂商对新用户扶持力度大,包年购买一台入门的 2 核 2G(2C2G) 轻量服务器通常只需数十元(价格随厂商活动变动,请以官方页面为准)。在选购云服务器时,规格选择 2 核 2G,系统镜像首选 Ubuntu 22.04 或 24.04 LTS(截至 2026 年 8 月),避开预装各种面板的系统,保持环境纯净。
你可以通过我的专属优惠链接直接选购:
💡 运维小经验:2C2G 的配置为什么够用? Linux 生产服务器无须运行图形界面,系统本身开销通常不到 200MB。Nginx 作为服务器时也很轻量,2GB 内存足以支撑个人站点和学习环境。
环境准备:SSH 登录与基建安装
SSH 连接服务器
购买云服务器后,你通常会拿到三类信息:
| 信息 | 作用 |
|---|---|
| 公网 IP | 从互联网访问这台服务器 |
| 登录用户 | 不同厂商系统可能不同,推荐使用默认用户 ubuntu |
| 登录密码或 SSH 密钥 | 用来证明“你有权限登录” |
连接服务器有两种常见方式。
方式一:在本地终端使用 SSH
这是最推荐掌握的方式,因为后续部署、上传文件、排查问题都离不开命令行:
ssh ubuntu@your-public-IP如果云厂商默认用户不是 ubuntu,就把命令里的 ubuntu 换成对应用户名,高权限操作通过 sudo 临时提权,能降低误删系统文件、误改全局配置的风险。
whoami可以查看当前登录用户。
首次 SSH 连接会提示确认主机指纹,输入 yes,然后输入服务器登录密码即可登录。
方式二:使用云厂商提供的 WebUI
阿里云、腾讯云等控制台通常都提供“远程连接”之类的浏览器入口(腾讯云侧常见为 OrcaTerm 等),不需要你本地先配好 SSH。
WebUI 适合第一次确认服务器能否登录。但它更像备用入口:后续 scp 上传、脚本化部署、CI 自动发布,都会更依赖本地终端里的 SSH。
密码登录和 SSH 密钥有什么区别
SSH 是连接服务器的协议,密码和 SSH 密钥是两种不同的认证方式。
| 认证方式 | 怎么理解 | 优点 | 缺点 |
|---|---|---|---|
| 密码登录 | 输入服务器账号密码 | 简单直观,适合初学者第一次登录 | 容易被弱密码、撞库、暴力破解影响 |
| SSH 密钥 | 本地保存私钥,服务器保存公钥 | 更安全,适合长期使用和自动化部署 | 初学者第一次配置稍微复杂 |
我的建议是:初学者第一次学习可以先用 ubuntu 用户 + 密码登录,跑通完整流程;一旦网站真正长期在线,就切换到 SSH 密钥登录,并关闭 root 密码登录。
💡 终端与 Shell:本章命令都会在终端里跑。建议花十分钟配好 Shell(代码高亮、自动补全等),大幅提升后续工作效率。具体配置见文末"延伸阅读"。
安装 Web 服务器 (Nginx)
在服务器上安装 Nginx(和本地一样的步骤):
sudo apt update
sudo apt install nginx验证安装:
nginx -v启动 Nginx:
sudo systemctl start nginx如果浏览器访问公网 IP 加载超时,优先检查云控制台的 或“防火墙”是否已放通 TCP 80 端口。也可以在本地终端快速验证服务与端口:
bashcurl -I http://your-public-IP返回
200 OK即表示服务和端口都正常——浏览器打不开多半就是安全组或本机网络的问题。
此时在浏览器访问 http://your-public-IP,应该能看到 Nginx 默认欢迎页面。如果不想打开浏览器,本地终端一行也能验:
curl http://your-public-IP | head输出含 Welcome to nginx 字样即说明页面响应正常。恭喜,你的服务器已经在公网可访问了。
首次发布:基于 scp 的手动发布
现在把本地的网站文件传到云服务器上。
本地构建
在本地项目目录中构建生产版本:
pnpm run docs:build构建产物在 docs/.vitepress/dist/ 目录下。
scp 上传到服务器
使用 scp 命令将构建产物上传到服务器:
scp -r docs/.vitepress/dist ubuntu@your-public-IP:~/这条命令做了什么:
scp:基于 SSH 的文件复制命令-r:递归复制目录docs/.vitepress/dist:本地构建产物目录ubuntu@your-public-IP:~/:复制到服务器ubuntu用户的家目录
上传完成后,登录服务器,将文件复制到 Nginx 静态资源目录:
sudo cp -r ~/dist/* /var/www/html/复制完成后做两步最小验证:
ls /var/www/html/ # 确认 index.html 等静态资源已就位
curl -s -o /dev/null -w "%{http_code}\n" http://your-public-IP # 应返回 200两步都通过就不需要重启 Nginx,刷新浏览器访问 http://your-public-IP,看到你的网站了——这就是发布到公网。
发布只是开始
至此,已经完成了一次完整的“本地 → 服务器”部署。流程是通的,但用不了多久你就会遇到这些问题。
痛点一:本地和服务器环境不一致
部署不是一步到位的。第一次把网站放到服务器上,大概率不会一次成功——Nginx 配置路径不对、权限不够、文件位置不对,都需要登录服务器反复调试。这意味着你需要同时维护本地开发环境和服务器生产环境两套配置。
回顾一下:本地 Mac 上安装 Nginx 用 brew install nginx,服务器 Ubuntu 上用 apt install nginx,连安装方式都不一样。配置文件路径也不同——Mac 在 /opt/homebrew/etc/nginx/,Ubuntu 在 /etc/nginx/。
好消息是,这些差异通常只需要首次部署时排查清楚,后续就稳定了。坏消息是,很差——如果你换了一台服务器(比如从阿里云换到腾讯云),或者换了操作系统版本,之前踩过的坑可能要重新踩一遍。环境配置的细节散落在操作记忆里,没有被固化下来。
在与出现之前,这种"散落"几乎不可避免——手动部署天然没有可复用的载体。真正可靠的做法,是把环境、交付步骤和验证方式逐步固化为可复用的配置与流程。
当前部署的是纯静态资源,跨平台传输没有问题。但涉及编译产物时,环境差异的影响会更加突出,我们在第 03 篇展开。
痛点二:每次发布都要手动 scp
软件交付其实分三个阶段,节奏完全不同:
| 阶段 | 环境 | 节奏 | 在干什么 |
|---|---|---|---|
| ① 开发迭代 | 本地(env.local) | 循环多次 | 改代码 → 本地预览 → 直到满意 |
| 构建 | 本地(env.local) | 验收后一次 | 将源码打包成可部署的产物(dist/) |
| ② 交付 | 本地 → 云服务器 → 公网 | 通常一次 | 上传 → 公网验证 |
构建属于开发环境的工作,但不在迭代循环内——它是"满意"之后的单次操作,是开发迭代与交付之间的边界动作。

本地热更新很快;真正费事的是验收后那一次跨机器交付——构建在本地,上传在云服务器,验证还要看公网。步骤一多就容易漏,表面都是"页面没变化"。
不过交付链本身可以模板化:构建 → 上传 → 验证。这就是 (Pipeline)的雏形——也是本系列对这一面建立起形式化表达的入口。
生产变更的数学表达
生产变更的本质是一组有序操作步骤:开发侧提交的内容变了,对应的发布流程就要重跑一遍。
一次生产变更可以形式化为一组步骤——白话说,就是一张操作清单:
其中,每个 是清单上的一项(构建、上传、验证……)。对于不同的开发提交 ,清单里的具体内容不同(如上传的文件、版本号),但清单的骨架保持不变——本篇就是固定的"构建 → 上传 → 验证"三步。
步骤之间的关系:有向无环图
步骤之间不是孤立的:上传必须等构建完成,验证必须等上传完成。 内部是一张有向无环图(DAG,Directed Acyclic Graph)。
发布步骤形式化为图 :
其中,节点 = 发布步骤。
其中,有向边 表示: 须等 完成后才能启动。
有向:依赖关系有且仅有一个方向——上传依赖构建产物,验证依赖上传完成,不能倒过来。
无环:任意步骤不能(直接或间接)依赖自身,否则流水线将死锁,永远无法结束。
合法执行顺序由 拓扑序(topological order) 给出:
并满足:
每个步骤必须在其所有前驱完成后才能启动。拓扑序存在当且仅当图无环——这正是"无环"成为硬约束的原因:有环就不存在合法执行序,流水线在定义层面就失效了。
本篇的 :最简线性 DAG
本篇的发布流程是最简的线性 DAG():
| 步骤 | 含义 |
|---|---|
| 构建 | |
| 上传 | |
| 验证 |
拓扑序唯一:。"先构建、再上传、后验证"不是约定俗成的习惯,是 DAG 约束下的唯一合法序。
💡 为什么没有"重启"或"重载"步骤? 本篇部署的是纯静态资源——直接覆盖
/var/www/html/下的文件即可。Nginx 读取的是磁盘文件,下次请求就拿到新版本,无需重启也无需 reload。如果变更涉及 Nginx 配置文件(
nginx.conf、sites-enabled/*),上传后需要追加两步:
sudo nginx -t:测试新配置语法(避免写错配置直接导致 Nginx 起不来)sudo nginx -s reload:平滑重载——master 进程保持运行,worker 平滑切换,不中断正在处理的请求此时 DAG 变成 5 步:构建 → 上传 → 配置测试 → 平滑重载 → 黑盒验证。
reload优于restart:前者不中断服务,后者会断开所有连接。验证放在最后一步是有讲究的——这里的"验证"是黑盒验证(从外部发 HTTP 请求,确认服务行为符合预期)。它必须等所有步骤都做完才能进行:reload 没跑、新配置没生效,外部请求拿到的还是旧行为,验证就失去了意义。
本次部署的 是最简的线性 DAG——下一步只改文案,这张图不变。但当流程出现并行分支(比如同时上传到多个区域),DAG 能表达"局部并行、整体有序"的约束,单纯的""写法做不到。把这些依赖关系明确下来,正是后续自动化交付能够稳定运行的基础。
痛点三:没有版本追踪
运维领域的常规做法是:部署前先备份当前版本,再覆盖新版本。比如把旧的 dist 重命名为 dist-20260713,然后再上传新的 dist。这样出了问题至少还能手动回退。
但手动备份有三个绕不开的断点:
第一,备份这一步本身会被跳过。 “这次改动很小,应该没问题”、“备份目录越来越多,占空间”、“上次备份了也没用过”——这些理由每次都成立,结果就是备份时有时无,一旦跳过就回不去。
第二,即使做了备份,也只是“一份旧的目录”。 dist-20260713 对应的是哪次代码变更?改了什么?只有日期,没有上下文。事后想排查“当时为什么挂”,找不到入口。
第三,回退要重做一遍部署操作。 出问题后,要手动把备份目录重命名回去,再手动重启服务。回退本身就是另一轮容易出错的手动操作——一次部署出错的概率不高,但部署 + 回退都要靠人不出错,概率就高了。
这三个断点叠在一起,手动备份有点像错题本:抄了但没看,看了又找不到,找到也用不上——形式上在做,实际上只是一套越来越繁琐的运维琐事。
更隐蔽的是版本不一致。本地构建的 dist 是基于当前代码生成的,但服务器上跑的 Nginx 配置、环境变量、依赖版本可能是上次部署时遗留的。你更新了 dist,却忘了同步 Nginx 配置;或者反过来,改了 Nginx 配置,但 dist 还是旧的。新旧版本混在一起,踩坑是常态——页面白屏、接口 404、样式错乱,排查半天才发现是前后端版本对不上。
手动备份解决不了 。 它只是把“覆盖”变成了“覆盖前先复制一份”,但“历史可追溯”、“变更可对比”、“回退可一键”这些能力,手动方式都给不了。这些不是备份的问题,是版本管理的问题——04 篇会看到,Git 正是为版本管理而设计的工具:每一次 git commit 都是带时间戳和说明的变更快照(解决“历史可追溯”),git diff 能精确比对任意两次提交之间的差异(解决“变更可对比”),git revert 一条命令就能回到任意历史版本(解决“回退可一键”)。
小结
生产环境的两个最低必要条件是 7×24 和公网 IP,云服务器是最短路径。通过 SSH 连接服务器 + scp 上传文件,就能完成一次最朴素的手动部署。
本篇把发布流程形式化为一组有序操作步骤——本例即"构建 → 上传 → 验证"的线性 DAG。手动部署能跑通,但每一步仍靠人执行。下一步进入服务端应用部署:当部署对象从静态文件升级成需要运行时的后端进程时,构建策略、进程生命周期与都会变成新问题。
思考
- 如果你的服务器宕机了,用户访问会发生什么?生产环境的“7×24”是由谁保证的?
scp和直接在服务器上改文件有什么区别?为什么我们选择在本地构建再上传?- 你在
scp时输错了目标路径,覆盖了服务器上的其他文件,怎么办?
延伸阅读:终端怎么选
终端 App 和 Shell 插件是两层叠加:App 负责长相,插件负责手感。选型优先 稳定、延迟低、习惯顺手——花哨功能其次。
终端 App
| 平台 | 默认 / 推荐 | 一句话特点 |
|---|---|---|
| Windows | PowerShell(系统自带) | 原生上手;命令习惯偏 Windows |
| Windows | Windows Terminal + WSL Ubuntu | 学 Linux 运维的默认组合 |
| macOS | Terminal.app(系统自带) | 够用,配 zsh 已现代化 |
| macOS | iTerm2 | 分屏、搜索、主题成熟 |
| macOS | Warp | AI 终端:补全、解释、工作流内置 |
关于 Windows Terminal 与 WSL Ubuntu: Windows 11 / 现代 WSL 安装时会自动把 WSL 注册为 Windows Terminal 的 profile,两者通常无需分开显式安装。如果你看到的是老版 Console Host 窗口,可在 Microsoft Store 单独装 Windows Terminal 后设为默认。Mac 默认 Terminal 与云服务器命令习惯完全兼容(都是 Unix 一脉),换 Warp / iTerm2 是体验升级,不是必需。
Shell 插件(zsh,Mac / WSL Ubuntu / Linux 都适用)
| 插件 | 作用 |
|---|---|
| oh-my-zsh | zsh 配置框架,主题 + 插件管理 |
| git | 大量 gst / gco / gp 别名 + 提示 |
| zsh-syntax-highlighting | 合法命令变绿、非法变红——输错当下就知道 |
| zsh-autosuggestions | 按历史灰字提示,→ 一键采纳 |
| zsh-completions | 补全 kubectl / docker / aws 等额外命令 |
最小可用组合:oh-my-zsh + git + zsh-syntax-highlighting + zsh-autosuggestions。Windows 原生 PowerShell 走自己体系(PSReadLine、posh-git),不通用。