LazyFork
Fork running containers with 1 MB of overhead.
A fork is an exact copy of a running container mid-execution: same processes, memory and filesystem, continuing from the same instruction. Forks can be forked again.
How it works:
- One CRIU dump of the container, many lazy restores from it.
- A memory page is copied into a fork only when the fork first touches it.
- Untouched memory is shared through the page cache and costs nothing per fork.
- The filesystem is an overlay stack; each fork writes to its own layer.
- No kernel or CRIU patches.
Install
- Environment macOS: LazyFork runs its daemon in a Linux VM managed by Lima. Linux: a host with CRIU 4.1+, userfaultfd, overlayfs and cgroup v2, for example Ubuntu 26.04.
- LazyFork
pip install lazyfork
Example
Start a container that counts, look inside it, fork it, and watch the fork continue the count:
lazyfork daemon start
lazyfork create python:3.12-slim --name counter -- \
python3 -c 'import itertools, time; [print(i, flush=True) or time.sleep(1) for i in itertools.count()]'
lazyfork attach counter # a shell inside: try `cat /var/log/lazyfork/output.log`, then `exit`
lazyfork fork counter --name twin
lazyfork attach twin # twin picks up the count where counter was; Ctrl-C to detach
Fork a 200 MB container 100 times
A container holding a 256 MB heap, forked a hundred times:
lazyfork create python:3.12-slim --name big -- \
python3 -c 'import itertools, time; heap = bytearray(256 << 20); [print(i, flush=True) or time.sleep(1) for i in itertools.count()]'
lazyfork fork big -n 100
lazyfork list # big: 260 MB; each fork: under 2 MB; all hundred together: 176 MB
lazyfork status
Only the original image is the full 260MB; forked containers only take ~1.7MB.
id name state parent depth pid procs memory image
────────────────────────────────────────────────────────────────────────────────────────────
1bef01acc3f2 big running - 0 5424 1 260.6 MB python:3.12-slim
c3e5f821c855 - running big 1 7175 1 1.7 MB python:3.12-slim
1fb2162825f9 - running big 1 7188 1 1.7 MB python:3.12-slim
a8c319f0ba0c - running big 1 7201 1 1.7 MB python:3.12-slim
dd6c193815e6 - running big 1 7214 1 1.7 MB python:3.12-slim
13bf7caf82bb - running big 1 7227 1 1.7 MB python:3.12-slim
...
Use
On Linux, run lazyfork daemon run as root. On macOS, the daemon lives in the VM and these commands manage it; everything else runs from the Mac.
lazyfork daemon start # creates and boots the VM; the first run takes a few minutes
lazyfork daemon status
lazyfork daemon clean # deletes all stopped containers
lazyfork daemon stop
lazyfork daemon delete # removes the VM and everything in it
Every container has a generated id, an optional name, and a parent. Commands take a name, an id, or a unique id prefix. lfork is a shorthand for lazyfork.
lazyfork create python:3.12-slim --name agent -- python3 -c 'import time; [time.sleep(1) for _ in iter(int, 1)]'
lazyfork fork agent -n 10 # ten running copies of agent, right now; agent keeps running
lazyfork fork agent --name worker # a named copy
lazyfork fork worker -n 5 # copies of a copy
lazyfork stop agent # snapshot and end the process; still forkable, holds no memory
lazyfork delete worker # its children keep running
lazyfork exec worker -- hostname # run a command inside
lazyfork shell worker # interactive shell inside
lazyfork attach worker # stream its output
lazyfork freeze worker # pause; thaw resumes
lazyfork list
lazyfork tree
lazyfork status # host, daemon and container totals
lazyfork stats worker # memory, page faults, restore timing; --csv for one row
from lazyfork.client import Client
agent = Client().container("agent")
with agent.fork(10) as forks:
outputs = [f.exec(["hostname"])["stdout"] for f in forks]
grandchildren = forks[0].fork(3)
Optional runtime hook, for runtimes that want a say in when they are forked:
- Listen on a unix socket inside the container, given with
--hook. QUIESCEarrives before a snapshot,RESEED <id>after a restore.lazyfork/hook.pyis a drop-in implementation;examples/agent.pyuses it.
Benchmark
Setup:
python:3.12-slimcontainer running a Python agent with a 128 MB heap.- Snapshotted once, forked 1 to 100 times, lazily and with a plain CRIU restore.
- Each fork runs 5 seconds, touching a few pages per second, then its cgroup memory is read.
- Lineage rows: forks of forks, nine levels deep.
- VM: 4 vCPU, 8 GiB, Ubuntu 26.04 arm64. Scripts and CSVs in
bench/.
| LazyFork | plain CRIU restore | |
|---|---|---|
| restore latency | 17 ms | 44 ms |
| memory per fork after restore | 1.4 MB | 138 MB |
| 100 forks | 435 MB | 13.8 GB |
| fork a running fork (snapshot + restore) | 170 ms | 210 ms |
| page fault at lineage depth 0 to 8 | 15 to 25 µs | none, all pages copied at restore |
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lazyfork-0.1.1.tar.gz.
File metadata
- Download URL: lazyfork-0.1.1.tar.gz
- Upload date:
- Size: 48.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.8.18
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bba4bfbb3dbacd2d7aab4b3182acdacc49db7f92c88672339d9224bc5712f895
|
|
| MD5 |
d075ea23565b86ca214caa63d3ce5bfc
|
|
| BLAKE2b-256 |
198f4939ded8440adf43f30171be174e680ada5928211561b57d01f83c2b4a22
|
File details
Details for the file lazyfork-0.1.1-py3-none-any.whl.
File metadata
- Download URL: lazyfork-0.1.1-py3-none-any.whl
- Upload date:
- Size: 50.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.8.18
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d8a051a33f809166a93b957ff4024bc57272f991bb5eed6f286f0c899c6e3ee
|
|
| MD5 |
eb843c8e01a17c71ba6669c60eefc7ed
|
|
| BLAKE2b-256 |
951f114e2e9f94e67feb77a74df5b7a1e99fb0265f5e50fb922a0becc7acd37a
|