Range
Use a remote environment before downloading it.
README.md

README.md

Use a remote environment before downloading it.

An environment is one file in object storage or on any HTTP server. Range opens a shell inside it without pulling it first. Only the blocks your workload reads cross the network.

Try it now. You need no account and no credentials.

# Apple silicon or arm64 Linux. On x86-64, use go1.23-amd64.range.
$ range shell https://github.com/andreygrehov/range/releases/download/demo/go1.23-arm64.range

Range environment
  Logical size      1.03 GB
  Ready             0.41 s

root@range:/go# go version
go version go1.23.12 linux/arm64
0.41 sto a shell
6.01 MBmoved, of 1.03 GB
278 MBthe whole artifact, stored

Measured on EC2 in us‑east‑1. From a laptop on home wifi: 1.76 s.

perf.log

3.9x smaller. 4.6x less network. 1.4x faster.

One dev environment (Ubuntu, Go, Node, Python and a Go repository with its module cache), stored two ways. Each ran go build ./... on a fresh EC2 host with nothing cached.

Plain EROFS image, cold1.0x
9.03 s406 MB
Range artifact, cold1.4x
6.47 s89 MB
Range artifact + profile3.2x
2.80 s90 MB
0 s5 s9.03 s

As a plain EROFS image, the environment takes 2.38 GB in S3. As a Range artifact, it takes 613 MB. The artifact moves a fifth of the bytes and still finishes first.

Against container lazy loaders on one host with the same build: SOCI took 9.23 s and eStargz with prioritized files 7.59 s and 101 MB. Range took 7.33 s and 89 MB cold, and 2.88 s with a learned profile.

The bars replay the measured time at 3x speed. Measured 23 September 2026 on an m6i.2xlarge in us‑east‑1. Every run was cold. Bytes come from the host network counters. Each value is a median of five.

problem.txt

What you wait for today

A machine that needs a large environment downloads all of it, every time, to use a small part.

CI pulls a whole image to run one test

The next commit pulls it again.

Fifty eval workers pull the same toolchain fifty times

All fifty download it in parallel, from the same bucket.

One changed dependency means a new image

You rebuild it, distribute it and roll it out before any worker can use it.

Range reads only the blocks each machine touches. It records them, and the next machine fetches them before it asks.

churn.log

Pre-baking is fast until something moves

One dependency added to a Go workspace, then shipped to five fresh EC2 workers in two ways.

From the change to five workers buildingRe-bake the imageRebuild the artifact
Make it distributable1082.4 s71.4 s
Launch until all five had built89.6 s28.7 s
Total1172.0 s100.1 s

The workers boot a 30 GB image with no environment on it. Each one read the new artifact from S3, was ready in 0.48–0.79 s and moved 90 MB. Range's own build took 54.5 s of the 71.4 s, and publishing took 2.2 s.

A changed environment no longer means rebuilt workers.

bench.log

Time to shell

The public demo, golang:1.23 as a 278 MB artifact, published as a GitHub release asset and as an S3 object. Time to shell is a signal sent from inside the environment just before your command runs.

ClientStateGitHubS3
MacBook, home wifiCold1.76 s1.88 s
Learned1.23 s1.36 s
EC2 arm64, us‑east‑1Cold0.41 s0.48 s
Learned0.38 s0.40 s
EC2 x86-64, us‑east‑1Cold0.35 s0.47 s
Learned0.36 s0.41 s

A cold start moves 6.01 MB in 24 requests. The whole artifact is 278 MB.

Measured 27 September 2026. Laptop rows are medians of three, EC2 rows (m7g.large and m6i.large) medians of five. Your latency will differ. The byte counts will not.

design.txt

One abstraction, and nothing above it

ReadAt(offset, length) -> bytes. Range does not interpret its bytes, so it works on anything that a byte offset can address.

Reads become range requests

Range caches fixed 1 MiB blocks on local disk. Concurrent reads of one block share one request. Adjacent misses merge, and sequential reads prefetch ahead.

The artifact is compressed, sparse and content-addressed

One object: a 64-byte header, 1 MiB chunks compressed one by one with zstd, and an index. Zero chunks take no space and identical chunks are stored once. Range checks every chunk against its SHA-256 on read. A 2.1 GB environment is a 613 MB file.

A shell is that, with a filesystem on top

artifact -> block device -> read-only EROFS -> writable overlay -> namespaces. The base never changes. Writes stay local, and one artifact serves every machine at once.

It learns the working set

Each session records which blocks it needed, and when. The next session fetches them in the background as the shell starts. A real read always goes first.

install.sh

Install

A release archive for macOS or Linux, x86-64 or arm64:

$ curl -fsSL https://github.com/andreygrehov/range/releases/latest/download/range_$(uname -s)_$(uname -m).tar.gz | tar -xz

Or from source, with Go 1.25 or newer:

$ git clone https://github.com/andreygrehov/range && cd range && make install

Then build an environment, publish it, and open a shell in it:

$ range build --from-oci golang:1.23 -o go.range
$ range publish go.range s3://<your-bucket>/go.range
$ range shell s3://<your-bucket>/go.range
$ range run s3://<your-bucket>/go.range -- go test ./...

Linux runs environments natively. macOS runs them in a Lima VM that Range creates itself. Windows works through WSL2, untested.

~/range $ view README.md