三個沙箱,每一個都從頭裝依賴、從頭跑資料前處理,跑到同一個位置才開始分岔,去試三種不同的修法。這是我設想的處境,不是誰的實際案例。

前面那一大段是三份重複的工作,分岔點才是你真正要的東西。我讀 microsandbox 的文件,就是想知道它怎麼讓「跑到一半的環境」變成可以留住、可以複製的東西。它是本機跑的 microVM 執行環境,專案描述寫的是 “easy, fast, programmable and local-first microVM runtime and library”,Apache-2.0,截至 2026-11-13 約有 9 千顆星,最新版是 2026-11-12 發的 v0.7.15。

這篇的依據只有文件。我讀了 README、release notes 和 快照文件,抓取日是 2026-11-13。沒有安裝過 msb,沒有跑過任何快照、fork 或 SDK 範例,沒讀原始碼,也沒量過任何時間或大小。所以下面每一句行為描述,都是「文件這樣寫」。README 自己也說這是 beta:「Microsandbox is still beta software. Expect breaking changes, missing features, and rough edges.」文件頁面是活文件,沒標對應哪個版本,預設值可能和 v0.7.15 當下有落差。

舊做法:重做一遍

我設想的舊做法很單純:環境壞了或要分岔,就重開一個,把前面的步驟重播一次。它的好處是每次都乾淨,缺點是你把「到達這個狀態的路」當成了狀態本身,路越長越貴。

microsandbox 的解法是把狀態拆成兩層留下來。

兩種快照,差在有沒有記憶體

文件把快照分成 Disk 與 Full,開頭一句就講明:「Disk is the default. Full adds memory and execution state; there is no memory-only snapshot.」我把它的對照表照搬成下面這張。

型態 存下什麼 還原時
Disk Files and owned volumes Boots a new VM
Full Disk, memory, and running processes Resumes execution

Disk 是預設,還原等於用存好的檔案開一台新機器。Full 連記憶體和執行中的行程一起存,還原是從中斷的地方繼續跑。文件還寫:「Each restore creates a new sandbox and leaves the source unchanged.」還原永遠長出一個新的沙箱,來源那份不動。

Full 有幾條邊界,都在同一頁。擷取時,執行中的來源沙箱會短暫暫停再繼續;已經暫停的維持暫停。還原後「The image’s startup command does not run again.」。連線的部分,文件說 microsandbox 不會擷取主機端的連線與傳輸,也不重播緩衝中的行程輸出,要你自己讓 client 重連。相容性要求用相容的 runtime 與 guest kernel,CPU 與記憶體設定(含上限)必須和擷取時一致。Full 也標了 Local-only,整頁頂部另有 Limited on cloud。

時鐘也有交代。預設的 sync 會讓沙箱的日期時間跟著主機,主機在開機時、大約每分鐘一次,以及 Full 快照還原或暫停中的沙箱恢復時更新時鐘。設成 off 的話,主機開機之後就不再更新,Full 還原會接續快照存下的時間。fork 繼承這個設定,單調時鐘(monotonic clock)兩種模式都從快照接著走。文件還特別提醒,舊版 SDK 與 CLI 會拒絕載入用 off 存的 Full 快照。

Full 預設不先 flush

flush 是把檔案系統裡還躺在記憶體的待寫變更,寫回磁碟。文件的預設策略叫 auto:「Flush before a running disk-only capture; no extra flush for full captures, forking, or pausing」。翻成白話,只有對執行中的沙箱做「Disk 快照」時才會先 flush;做 Full 快照、fork、暫停時,不會多做這一步。文件的解釋是,Full 快照與 fork 把這些變更留在記憶體裡。

這很合理,因為 Full 本來就帶著記憶體走,變更不會丟。問題出在你之後想換一種方式還原。文件寫得很直接:「Choose required if you plan to restore a full snapshot’s disks without its memory」。也就是說,你今天為了保住行程狀態存了 Full 快照,之後想退而求其次,只把它的磁碟還原成一台新機器,那些當初只在記憶體裡的寫入就可能不在磁碟上。文件對只還原磁碟的描述是:保留存下來的檔案,但會失去執行中的行程、RAM、內層 VM 的執行狀態,以及「writes buffered only in memory」。

三個策略的差別,我照文件的表整理成一句話。auto 是上面那樣;required 要求擷取或暫停前 flush 成功;skip 則完全不 flush,文件警告 Disk 快照可能漏掉緩衝中的寫入,需要檔案系統修復。

我沒有實際去驗證這個行為。我讀到的只有文件這幾句,沒有原始碼,也沒有跑過「不加 required 時實際會丟哪些寫入」。所以我能負責的說法是:文件把這個坑明寫在預設值旁邊,而預設值的選擇邏輯(Full 不需要 flush)跟「之後只還原磁碟」這個使用情境互相打架,要靠你在建立快照的當下想到。

另外兩條相關的邊界,文件同樣明寫。第一,flush 只管檔案系統,「Flushing does not save data still buffered by your application or commit database transactions」,應用程式自己的緩衝與資料庫交易,要你先存好、先 commit。第二,Full 還原「never silently falls back to disk-only recovery」,不會偷偷降級成只還原磁碟;以 tmpfs 當 root 的快照,則根本不能只還原磁碟。

其他部分講的是「功能存在」,這一條講的是預設值的後果在很久以後才冒出來,正好是你最不會回頭檢查的時間點。

fork:複製的是正在跑的那一刻

快照是存起來以後再說。fork 是現在就複製。README 的說法是:「Forking duplicates live execution. Restoring starts from saved state; add --cow-mem to share unchanged memory from a full snapshot.」fork 的指令是 msb fork app --name app2。

把裝完依賴、前處理跑完的沙箱 fork 三份,各自走不同的修法,大概就是 fork 對得上的用法,不過這是我的聯想,文件沒有寫這個情境。如果不想現場 fork,而是把狀態存成 Full 快照,之後一次還原出好幾個子沙箱,--cow-mem 就派上用場。文件的說法是 Full 還原通常會複製記憶體,開啟 copy-on-write 之後,子沙箱之間共享沒改過的頁面,各自的寫入保持私有。第一次還原會先準備一份本機的記憶體快取,之後的還原重用它。限制也寫了:CoW 需要 Full 快照,不能和只還原磁碟的做法併用。文件範例是對同一個快照連續執行兩次 msb snap restore worker:snap --name worker_a --cow-mem、--name worker_b --cow-mem。

我沒量過這能省多少記憶體或時間。文件沒給數字,我也沒有。

改名留下了一條線索

v0.7.5(2026-09-30)的 release notes 有一行:「feat(sdk): add fork naming with deprecated compatibility aliases」,PR 是 #1708。README 說舊的 --forked 還活著,只是被標成 deprecated alias。文件的「Older restore option names」表對得很細:

CLI 的 --forked 變成 --cow-mem;TypeScript 的 .forked() 變成 .cowMemory();Python 的 forked=True 變成 cow_memory=True;Rust 與 Go 也各有對應的新名字。舊名「still enable copy-on-write memory during restore」,文件要你在新程式碼用新名,MCP 的 sandbox_restore 工具則保留 forked 這個輸入名稱。

為什麼改名,原文沒說。我的推測是:舊名讓人把「還原」和「fork」讀成同一件事,而兩者在文件裡是兩個動作,一個複製活的執行,一個從存好的狀態開始、可選擇共享記憶體。這只是推測,唯一的證據是舊名與新名的字面落差。如果之後在 PR #1708 的討論裡看到改名另有原因,我就收回這段。