Skip to main content

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

  1. 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.
  2. 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 system stats

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.

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 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.
  • QUIESCE arrives before a snapshot, RESEED <id> after a restore.
  • lazyfork/hook.py is a drop-in implementation; examples/agent.py uses it.

Benchmark

Setup:

  • python:3.12-slim container 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

lazyfork-0.1.0.tar.gz (47.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lazyfork-0.1.0-py3-none-any.whl (47.3 kB view details)

Uploaded Python 3

File details

Details for the file lazyfork-0.1.0.tar.gz.

File metadata

  • Download URL: lazyfork-0.1.0.tar.gz
  • Upload date:
  • Size: 47.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.18

File hashes

Hashes for lazyfork-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d50cb1b5af1dfa9dc930127230a7e7ac4759ccc29fdff1a99cc3d10400527169
MD5 37950b9954608e54364f4eff765b018e
BLAKE2b-256 8acdbdd7b56ddaa17814dfb761fea8bd8095c8138a19a6bb66041a1a53e7fc5f

See more details on using hashes here.

File details

Details for the file lazyfork-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: lazyfork-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 47.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.18

File hashes

Hashes for lazyfork-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8d73c4db9a3d7a8f84560bfa43cf599a4f5baa1a9d95497f9f3daeddcf44b7c6
MD5 8061a9fd1565a6b7cab1dc2837d24ade
BLAKE2b-256 99db6f15ab7e353be416bada945356b82da6247fe0f3332b8789c3b0a7c22e93

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.1

2 files

0.1.1

2 files

This release

0.1.0 This release

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page