周三下午,我坐在一家吵吵嚷嚷的餐厅里。背包里的 MacBook Air 完全关机,连待机模式都没开。我掏出安卓手机,打开自己架设的 Codex 编码代理,对着它说了一句:“改一下那个 Expo 应用里的某个页面。”

仓库不在手机上,也不在休眠的 MacBook 里。它躺在我家一台正在跑 Ubuntu 的旧游戏 PC 上,一起躺在那里的还有编码代理、后端服务、数据库、Metro 打包器和安卓构建工具。改动准备好之后,我从聊天界面切回同一个手机上的应用,直接看到了效果。

打开网易新闻 查看精彩图片

然后我意识到一个有点反直觉的事情:本地开发这个动作,其实早就不在“这台电脑”上发生了。

这件事的起点不是什么宏大的技术规划,而是一个特别现实、甚至带着点寒酸的理由:我手里这台旧 PC 的 16GB 内存不够用了。Ryzen 5 5600G、1TB 存储——配置就是那种你拿来打打游戏、写写脚本的个人主机,不是专门买来跑 AI 智能体的工作站。

最早我试图在 Windows 上面凑合着干:开一个 WSL,跑 Docker,然后从 16GB 的 MacBook Air 远程桌面连过去。分开看每个组件都说得通,凑在一起就是一种缓慢的折磨:Windows 要吃内存,WSL 要吃内存,Docker 也要吃内存,而这台机器还得花额外的算力去渲染一个我并不想看的远程桌面画面。

能用,但喘不过气。机器把有限的能力全花在“伺候开发环境”这件事上,真正我想跑的服务反而被挤到墙角。一怒之下我直接给塔式机装了 Ubuntu,目标非常朴素:让这台机器只关心代码仓库、Docker 容器、数据库、开发服务器、打包流程和那堆需要一直跑着的智能体工具。我的 MacBook?继续做它本来就擅长的事——开浏览器、打编辑器、处理所有需要我直接盯着的交互。

我没做一个“换系统前后内存对比”的科学测试,也不想假装这里有严谨的数据。但从我敲键盘的体感来说,这种任务拆分比之前的拥挤状态明显舒服了不止一点。从那之后,我把整个开发环境拆成了两块。

MacBook Air 是我的主客户端。VS Code、Codex、浏览器、通讯工具都在上面跑。Ubuntu 塔式机则负责“扛活”:OpenSSH、Docker、PostgreSQL、后端服务、前端热更新服务器、Metro、安卓构建指令,以及所有需要真正操作那些仓库的智能体工具。两台机器之间用 Tailscale 拉了一条私有网络,各自配了固定的名字。我没有用 Tailscale 自带的 SSH 功能,而是在这条私有网络之上跑普通的 OpenSSH——这样更顺手,也减少了多余的依赖。

接下来最关键的一步来自 VS Code 的 Remote SSH 扩展。我从 MacBook 上打开一个托管在 Ubuntu 上的仓库,那种体验几乎就像在操作本地文件。集成终端跑的是 Ubuntu 里的 shell;需要访问代码的扩展也跑在那台机器上;启动开发服务器之后,端口可以自动转发回来。写代码这个动作的“物理位置”已经飞快地朝那台旧游戏主机滑了过去,只把界面留在了我面前的屏幕上。

Andrej Karpathy 以前提过一个概念,叫“能直接面对编辑器的那个环境才是本地”。那一天在餐厅里,我发现这个定义也得更新一下:编辑器面对的环境也可以不在你手边,只要它离你的代码够近就行。我甚至可以说得更直白一点——当我按下保存的时候,文件写在哪台机器的硬盘上,哪台机器就是本地。

这种转变拆开来其实就五个环节,每个环节单独看都不新鲜,但拼在一起之后就改变了“我在哪儿工作”这个最基本的感觉:

第一步:把重型进程扔给专属主机。别再用那台你每天带着跑的笔记本电脑同时干所有活。让一台不关机的机器去扛 Docker 容器、数据库和构建任务。这不需要服务器级硬件,一台吃灰的游戏 PC 已经绰绰有余。

第二步:用 Tailscale 把所有设备塞进同一个私人网络。不用折腾公网 IP,也不用操心证书。给每台设备一个稳定的主机名,OpenSSH 可以直接走这条隧道。

第三步:让编辑器远程工作,本地只留界面。VS Code Remote SSH 能做到的事比你想象的要多:远程终端、远程扩展、远程端口转发。你在本机打字,而真正的计算发生在仓库所在的机器上。

第四步:把构建和预览结果直接映射回手机。安卓打包工具在远程机器上跑着,Metro 热更新也在远程机器上跑着,输出可以直接被本地网络内安装的应用访问到。从修改代码到在真机上看到变化,中间的路由变得极短。

第五步:忘掉“这台机器配置够不够”这个念头。当你不再把“开发环境必须和手里这台设备绑定”当成前提,硬件的选择空间就突然放大了。旧主机可以继续服役,轻薄的笔记本可以只做轻的事。

至于那次在餐厅里,我其实没有提前设想什么。我只是打开手机上的编码代理,随口下了一个指令。改动提交之后,应用更新的版本直接在同一个手机里呈现了出来。整个过程安静到连隔壁桌在争论要不要加辣的声音都更抢戏。真正让我心里震了一下的,是后面那个念头:我的笔记本当时是关机的,那么“本地”到底在哪儿?

答案比我想象的坦然:它早就不在膝盖上,也不在背包里。它在我家里那台一直开着的、嗡嗡作响的旧 PC 上,正在安静地帮我跑着 Metro、ADB 和 Expo 的打包链路。所谓本地,不过是谁握着代码仓库和运行环境这两个把手。