← writings

How to fork a computer

Every programmer knows fork(). You call it, and your one process becomes two, both continuing from the same line of code with the same variables, and from that moment they go their separate ways. It's one of the oldest tricks in Unix. What fewer people know is that you can do the same thing to an entire computer. Not copy its disk. Fork it. Take a machine that is running right now, with a browser open and a program halfway through a loop, freeze it at one exact moment, and wake up two of them.

Left: a Unix process forks into two processes that continue from the same line. Right: a whole running computer forks into two computers that continue from the same moment. fork() a process x = 41 x = 41 x = 41 same variables, same next line, then they diverge fork a computer Chrome open loop at i = 41 i = 41 i = 41 same tabs, same RAM, same next instruction
Same idea, bigger unit. The process version is fifty years old. The computer version is what this post is about.

I've spent the last few months reading every doc and paper on this, and most of them are written for people who already run data centers. This is the version I wish I'd had at the start. Plain language, no hand-waving about what's actually being copied, and honest about where the easy version stops and the hard version begins.

First, what is a running computer made of?

When you save a file, you're saving disk. When you take a "snapshot" of a VM in most cloud consoles, you're also mostly saving disk. That's not a fork. If you boot a copy of the disk, you get a fresh machine that has to start from zero: kernel boots, services start, your app loads, caches fill. You've cloned the hard drive, not the moment.

The moment lives in four places. The disk, sure. But also the memory, which is every open program, every variable, every browser tab, the kernel's own bookkeeping. The CPU registers, which are the handful of numbers that say exactly which instruction each core was about to run and what it was holding in its hands when you froze it. And the device state: what the network card was in the middle of sending, which keys the keyboard controller thinks are held down, where the clock is. Fork all four and you have the moment. Fork fewer and you have a reboot.

A running machine is four kinds of state: disk, memory, CPU registers, and device state. A disk copy captures only the first. A fork captures all four. DISK COPY disk · files, OS, installed apps memory · gone CPU registers · gone devices · gone boots from zero. the moment is lost. FORK disk · files, OS, installed apps memory · every open program CPU registers · the next instruction devices · network, clock, input wakes up mid-sentence. the moment survives.
A snapshot in most cloud consoles is the left column. A fork is the right one.

The trick that makes it fast

Here's the obvious objection. A machine with 8GB of memory has 8GB of memory. If forking means copying all of it, a fork should take as long as copying 8GB, which is seconds at best. But real forks happen in single-digit milliseconds. How?

The answer is that you don't copy. You share, and you only copy the parts that change. This is called copy-on-write, and it's the same trick the original fork() uses. When you fork a process, the kernel doesn't duplicate its memory. It marks every page (memory is handed out in 4KB pages) as shared and read-only, and lets both processes read the same physical pages. The instant one of them tries to write to a page, the kernel catches it, quietly makes a private copy of just that page, and lets the write go through on the copy. The other process still sees the original. Ten forks of a 500MB process cost almost nothing until they start writing, and even then they only pay for the pages they touch.

Copy-on-write at the page level. Parent and child point at the same eight memory pages. When the child writes to page 5, only that page is copied. The other seven stay shared. before the child writes anything parent child 8 pages, one copy, both point here after the child writes to page 5 parent child 5' child still shares the other 7 pages
One write, one page copied. This is why ten forks of a big program cost almost nothing.

Forking a whole machine does the same thing one level down. The hypervisor, which is the program that pretends to be hardware for the VM, saves the guest's memory to a file once. Then every child maps that file as its memory, read-only, shared. When a child writes to a page, the host gives it a private copy of that page. The parent's memory file never changes. A child that touches 50MB of memory in its first minute costs 50MB, not 8GB. And the fork itself is just setting up the mapping, which is why it's milliseconds.

Two honest caveats on that number, because the marketing pages leave them out. First, "the child appears in milliseconds" and "the child is doing useful work in milliseconds" are different claims. Setting up the mapping is fast. But the moment the child runs, it starts touching memory, and every page it touches for the first time has to be pulled in from the file. If that file is on a local SSD and warm in the host's cache, that's quick. If it's cold, or on a network disk, the child spends its first few hundred milliseconds stuttering through page faults before it feels alive. The {L('https://dl.acm.org/doi/10.1145/3492321.3524270','FaaSnap paper')} is a good read on exactly this gap. Resume latency is what gets quoted. Time-to-useful-work is what you actually feel.

Timeline after a fork. The child is marked as running within a few milliseconds, but spends the next stretch faulting in memory pages and reconnecting before it is doing real work. 0 ms ~500 ms "resumed" the number on the slide page faults · disk reads · fixing its own identity · reconnecting actually working time to useful work the number you feel how long the grey part is depends on where the memory file lives and how cold it is.
Both numbers are true. Only one of them is on the pricing page.

Second, sharing memory only works when the children are on the same physical machine. Two VMs on the same host can literally read the same RAM pages. Two VMs on different hosts cannot, because RAM doesn't stretch across a network. What you can do across hosts is share the file: chop the snapshot into chunks, store them in a cache, and have each child pull only the chunks it needs, on demand. {L('https://www.buildbuddy.io/blog/fast-runners-at-scale/','BuildBuddy wrote up')} a system that does this for CI runners. It's the same copy-on-write idea, just with a network in the middle, and every host still has to end up holding its own copy of whatever pages its child touches. So: one snapshot, many children on one box is cheap. One snapshot, many children across a fleet is a real distributed systems problem.

A parent machine runs to a milestone and is frozen. Two children are created from that moment. Both share the parent's memory read-only and each gets a small private area for its own writes. parent frozen here boots, logs in, opens the app, warms the cache parent memory file saved once · read-only · shared child A its own writes: a few pages it touched child B its own writes: different pages, same parent both read from here grey is shared with the parent. red is the only memory each child actually owns.
The fork is cheap because almost nothing is copied. Each child pays only for what it changes.

The disk works the same way. Nobody copies the disk image. Each child gets a thin overlay file that starts empty, and any block the child writes goes into the overlay. Reads check the overlay first and fall back to the parent's disk. QEMU calls these qcow2 backing files. Docker's layered images are the same idea. The parent disk is frozen forever, and each child's overlay is its diff.

Disk forking with overlays. One frozen parent disk at the bottom. Each child has a thin overlay on top holding only the blocks it wrote. Reads fall through to the parent when the overlay is empty. parent disk · frozen · read-only · 20 GB child A overlay ~30 MB child B overlay ~50 MB read: check my overlay, else fall through to the parent. write: always into my overlay.
Two "20 GB" children that together use about 80 MB. Docker layers work exactly like this.

The four ways people actually do it

Firecracker is what I'd point anyone at first. Amazon built it to run Lambda, and it's a tiny hypervisor that boots a stripped-down VM in about a hundred milliseconds. Its snapshot support is the cleanest version of the fork I've described. You pause the VM, ask it to write out two files, one for memory and one for everything else (CPU, devices), and then you can start as many new Firecracker processes as you want from those two files. Each one maps the memory file privately, so writes are copy-on-write for free, courtesy of the Linux kernel. The API is three HTTP calls:

# freeze the parent and save it
PUT /vm            {"state": "Paused"}
PUT /snapshot/create {"snapshot_type": "Full",
                      "snapshot_path": "vm.state",
                      "mem_file_path": "vm.mem"}

# in a brand-new firecracker process, wake a child from it
PUT /snapshot/load {"snapshot_path": "vm.state",
                    "mem_backend": {"backend_type": "File",
                                    "backend_path": "vm.mem"},
                    "resume_vm": true}

Run that last call ten times and you have ten children, all mid-sentence, all sharing one memory file. This is what E2B and most of the newer sandbox companies are doing under the hood when they advertise sub-200ms starts. One constraint the docs are blunt about: a snapshot only restores on a host with the same Firecracker version, same kernel, same CPU features, same device layout. It's a saved brain, and it only fits back into an identical body. "Resume anywhere" really means "resume anywhere in a pool of matching machines," which is a fine thing to build, but not the same thing.

VMware Instant Clone is the enterprise version, and honestly it's the one that made the word "fork" stick for VMs. Their marketing used to literally call it VMFork. You keep a parent VM running, and the child is created from the parent's live memory and device state without ever going through a boot. The child shares the parent's physical RAM and disk via copy-on-write and is usable in a second or two. VDI shops (virtual desktops for office workers) use it to spin up a thousand identical Windows desktops every morning from one warmed-up parent, which is the same "a thousand agents from one prepared machine" story wearing a suit.

QEMU/KVM is the general-purpose Linux hypervisor, and it can do all of this, but with more assembly required. You save the live state of a running VM with savevm or virsh snapshot-create-as, which writes memory and device state into the disk image or a file. To fork rather than just restore, you point multiple new VMs at that saved state with their own qcow2 overlays for disk. QEMU won't share memory between children by default the way Firecracker does, so each restore reads the full memory back, and you lose the millisecond number unless you do extra work. It's the right tool if you need a full-fat VM with GPUs and weird devices. It's the wrong tool if you want a thousand forks.

CRIU, Checkpoint/Restore In Userspace, is for when you don't want a whole machine, just a process or a container. It freezes a running Linux process tree and dumps everything about it, memory, open files, sockets, even TCP connections, into a folder. Restore that folder and the process picks up where it stopped, on the same machine or a different one. Restore it three times and you've forked the container. It's heavier than the VM approach because it's a real save-then-load rather than a memory mapping trick, but it works without a hypervisor and it's what Docker and Podman use for checkpointing.

Comparison of Firecracker, VMware Instant Clone, QEMU/KVM, and CRIU on what they fork, how fast a child appears, and whether children share memory with the parent. tool what it forks child appears in shares memory? Firecracker a microVM milliseconds yes, via mmap VMware a full VM, live about a second yes QEMU/KVM a full VM seconds not by default CRIU a process or container hundreds of ms no the fast ones share. the slow ones copy. that's the entire difference.
Pick by how many children you need. One or two, anything works. A thousand, you need sharing.

The children wake up confused

This is the part the tutorials skip, and it's the part that actually bites you. A child is a perfect copy of the parent at the frozen moment. Perfect is the problem. The parent had a network card with a MAC address, an IP address, a set of open TCP connections, a random number generator that was seeded at boot, a system clock, a hostname, maybe a TLS session or an SSH connection. Every child inherits all of it, bit for bit.

So you wake up ten children and you have ten machines with the same MAC address on the same network, which the network hates. Ten machines that think it's 2:14pm when it's actually 2:31. Ten machines with an open TCP connection to a server that only remembers one of them, so nine of them are talking into a dead socket and don't know it. And worst of all, ten machines whose random number generators are in the exact same state, so they'll generate the exact same "random" session tokens, the same nonces, the same temp file names. If any of them mints a cryptographic key right after waking, all ten mint the same key. Firecracker's own docs have a page specifically about this, and it's not a footnote.

Right after a fork, two children hold identical values for their MAC address, IP, clock, open connections, and random seed. Each must be fixed before the child does anything. inherited child A child B fix MAC + IP 10.0.0.7 10.0.0.7 re-assign on wake clock 2:14:07 pm 2:14:07 pm resync to host open TCP → db:5432 → db:5432 expect it dead, reconnect random seed 0x9f3a… 0x9f3a… reseed before anything hostname agent-01 agent-01 rename
Every row is identical at birth. The fork is the easy part. Telling the children apart is the work.

So every real forking system has a "wake-up" step that runs inside the child immediately after it resumes. Give it a new MAC and IP. Reseed the random number generator. Sync the clock. Tell the guest its hostname changed. Expect every old connection to be dead and reconnect. VMware runs a customization script for this. Firecracker's docs tell you to handle it in the guest yourself. And that word, "yourself," is doing a lot of work. The hypervisor can fix the things it can see: network card, clock, the entropy pool. It cannot see inside your programs. It has no idea that your app holds an OAuth token in a variable, that Chrome has a logged-in session in memory, that Postgres has a connection open with a password in it, or that a background job just generated an encryption key. All of that is duplicated too, and nothing at the hypervisor level knows it exists, let alone how to rotate it. You write that part, per application, and you will get it wrong at least once in a way you only discover when two children try to talk to the same database.

There's a deeper version of this problem that I wrote about in the three layers of agent persistence. The MAC address is a small identity problem. The child also inherits every logged-in session, every API token in memory, every cookie in the browser. Now you have ten agents that all look like the same user to every service they touch. Forking the machine on one host is a solved problem. Deciding what the children are allowed to be is not.

The four levels, and where the solved part ends

Once you've read enough of this, you notice that "fork a computer" is being used to mean four different things of very different difficulty. It helps to name them.

Four levels of forking a computer, from starting many VMs from one image up to a distributed graph of live state, with difficulty rising at each level and the top level having no complete public solution. 1. Cold clone on one host start many VMs from one prepared snapshot. Firecracker restore, cloud images. solved. 2. Live fork on one host freeze a running machine, children share its RAM and disk. VMware Instant Clone, E2B, Orgo. hard but established. 3. Restore on a different host move the state over a network, fetch pages lazily, rebuild devices, match CPU and kernel. harder. 4. A branchable graph of live state across a fleet fork, sleep, migrate, dedupe, resume near spare compute, keep identity and side effects sane. no complete public answer yet. everything in the tutorials is levels 1 and 2. everything interesting is 3 and 4.
Same phrase, four problems. Be suspicious of anyone who won't tell you which level they mean.

Level one is a solved problem and has been for years. Level two is what this whole post has been about, and the tools exist. Level three is where it stops being a hypervisor feature and becomes a storage and scheduling problem: the state has to outlive its host, move over a network, and only land on a machine with a matching CPU, kernel, and device layout. Level four is level three plus time. The state isn't a file you restore once. It's a tree that keeps growing, with branches that sleep for hours, wake up on whatever machine has room, get forked again, and share as much as possible with their siblings along the way. Nobody has a complete, public, general version of that yet.

What a real fork would have to carry

Here's the reframe that made the gaps obvious to me. A snapshot today is two files: memory and machine state. But if you think about what a child actually needs to wake up safely, the list is longer, and everything after the first two items is where the unsolved work lives.

It needs the memory and CPU state, obviously. It needs the disk layers. Then it needs a compatibility manifest, a note saying which hosts this can even be restored on. It needs an identity contract: which of the inherited logins, tokens, and keys this branch is allowed to keep, and which get revoked or swapped on wake. It needs a network contract: what address it gets, what it may connect to, whether the dead connections are reconnected or refused. It needs a time and randomness policy, and here's a fun wrinkle, the right policy depends on what you're using the fork for. If you're running a reinforcement learning experiment and want ten branches to be exactly reproducible, you want the clock and the random seed to be identical across children. If you're running ten production agents, identical randomness is a security hole. Same mechanism, opposite requirement. And finally it needs some record of external side effects, because if the parent sent an email or charged a card before the fork, ten children continuing from that moment must not each do it again.

What a snapshot holds today versus what a safe, portable state object would need to carry. Today: memory and machine state. Needed: also disk layers, a compatibility manifest, identity contract, network contract, time and randomness policy, and a side-effect journal. a snapshot today memory file CPU + device state disk overlay enough to wake up a portable state object memory · CPU · devices · disk layers compatibility manifest · where can I run? identity contract · which logins do I keep? network contract · who may I talk to? time + randomness policy · same or unique? side-effect journal · what did I already do? enough to wake up and be trusted blue = nobody's hypervisor does this for you
The grey rows are solved. The blue rows are the gap.

None of that is exotic. It's the kind of bookkeeping distributed systems people do all day. But none of it lives at the hypervisor layer, and none of the four tools above will do it for you. That gap, between "we can copy the bytes" and "the copy is safe to let loose," is the actual frontier.

Where this is going

All four tools above were built for other reasons. Firecracker for serverless functions, VMware for virtual desktops, QEMU for general virtualization, CRIU for migrating containers between servers. None of them were designed for the thing that's suddenly pulling on them, which is an AI agent that's spent twenty minutes getting a machine into a useful state and now wants to try three things from there at once.

That use case is different in one specific way: the fork isn't a rare event. Serverless forks once per warm start. Agents want to fork before every risky action, every time they're unsure, every time a task has two plausible next steps. That pushes the numbers down from "milliseconds is fine" to "single-digit milliseconds, hundreds of times an hour." The research is already there. TClone forks a live desktop with the GUI still running. DeltaBox gets checkpoint and rollback to about 14 and 5 milliseconds by only ever saving the delta and forking from a frozen template process. Both are copy-on-write again, applied more aggressively.

The mechanism is not new. Unix figured out that you fork by sharing and copying only on write, fifty years ago, for processes. Hypervisors borrowed it for machines. The thing that's new is who's asking, and how often. If you understand that one trick, sharing until someone writes, you understand every system on this page. The rest is plumbing and the very real headache of ten machines that all think they're the same machine.

If I had to compress all of this into one sentence, it's this: a running computer should be a portable state object, not a machine. We're not there yet. But every tool on this page is a step toward it, and the steps are getting shorter.

← more writings