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

README.md

Use a remote environment before downloading it.

Range opens a shell in a container image, a Hugging Face repository, or an environment in S3 or on any HTTP server, without downloading it first. Only the bytes your program reads cross the network.

$ range shell python:3.12
$ range shell python:3.12 --mount hf://moonshotai/Kimi-K2-Instruct:/model
$ range shell s3://<your-bucket>/dev.range
$ range shell python:3.12 --mount .:/work -p 8000:8000
$ range shell ebs://ami-0123456789abcdef0
$ range shell ubuntu --commands
2.8 sto run Python in python:3.12
48 MBmoved, of a 435 MB image
9.5 MBread, of a 1.03 TB model

No Docker, no daemon and no pull. Each session runs in a small VM of its own, on Linux and on a Mac.

Measured on EC2 in us‑east‑1, with the image indexed once. See bench.log.

demo.txt

A chat model, from nothing, in one line

$ range run ghcr.io/ggml-org/llama.cpp:light-b11206 \
    --mount hf://unsloth/gemma-3-270m-it-GGUF:/model -- \
    llama-cli -m /model/gemma-3-270m-it-Q4_K_M.gguf -st \
    -p "Why is the sky blue? Answer in one sentence."

The sky is blue because of a phenomenon called Rayleigh scattering,
where blue light is scattered more than other colors.

Range opens the llama.cpp image from its registry and mounts the model repository at /model. The repository holds 6.38 GB in 24 files. Range reads one of them. With the image indexed, the answer took 6.7 s from an empty cache. docker pull plus hf download took 18.3 s. The image comes pre-indexed from a public catalog, so a new user starts there.

The same chat, on your GPU

$ sudo range run --gpus all ghcr.io/ggml-org/llama.cpp:light-cuda-b11206 \
    --mount hf://unsloth/gemma-3-270m-it-GGUF:/model -- \
    llama-cli -m /model/gemma-3-270m-it-Q4_K_M.gguf -ngl 99 -st -p "Hi"

Range keeps the kernels CUDA compiles for your GPU. On a Tesla T4, every run after the first answered in 3.1 s. With Docker, every run took about two minutes.

A 1 TB model, open in seconds

$ range run python:3.12 --mount hf://moonshotai/Kimi-K2-Instruct:/model -- \
    du -sh --apparent-size /model
959G    /model

Kimi K2 is 1.03 TB in 61 shards. A Python script inside read its config, the header of one shard and one tensor. That took 3.4 s with the image indexed, and moved 9.5 MB of the model. The other 60 shards never left Hugging Face.

Range reads a file when a program opens it. A program that reads a whole model still downloads the whole model, once.

A server's disk, from your laptop

$ range run ebs://snap-0fb55b083ff371aff -- \
    sh -c 'grep PRETTY /etc/os-release; dpkg -l | wc -l'
PRETTY_NAME="Ubuntu 24.04.5 LTS"
641

Range opens an EBS snapshot or an AMI with no instance and no volume. From a MacBook, the answer took 6.3 s. Starting an instance from the AMI took 21.9 s.

Every command, any version

$ range shell ubuntu --commands
root@range:~# python3 -c 'print(6*7)'
[range] python3 from python:3.12-slim
42
root@range:~# go@1.22 version
go version go1.22.12 linux/arm64

A command Ubuntu lacks comes from the image that has it. go@1.22 picks the version, and so does a project's go.mod or .nvmrc.

agent_sandbox.txt

A sandbox for an agent

$ id=$(range run -d --commands --cpus 2 --memory 4g \
    --allow-host pypi.org --allow-host files.pythonhosted.org ubuntu)
$ range cp ./repo $id:/root/
$ range exec -w /root/repo $id go test ./...
ok      example.com/demo    0.001s
$ range exec -it $id bash
$ range stop $id
0.71 sto a ready VM, image cached
0.89 sto start ten of them
0daemons to install

A VM of its own

Each sandbox boots its own small VM, on Linux and on a Mac.

Every tool, any version

go, pip or nvcc come from the image that has them.

Only the hosts you allow

--network none cuts it off. --allow-host opens one host.

Docker's commands

run -d, exec, cp, ps, stop, --cpus, --memory.

bench.log

The chat demo, against docker pull

docker pull + hf download1.0x
18.27 s579 MB
Range, first run1.2x
15.46 s590 MB
Range, image indexed2.7x
6.73 s317 MB
Range, again4.3x
4.25 s0 MB
0 s10 s
Empty cache to outputdocker pullRange, first runRange, indexedRange, again
Chat demo18.3 s 579 MB15.5 s 590 MB6.7 s 317 MB4.2 s 0 MB
python:3.1215.8 s 435 MB16.5 s 415 MB2.8 s 48 MB1.1 s 0 MB
rust:1.8219.6 s 569 MB22.4 s 546 MB8.0 s 125 MB1.6 s 0 MB
eclipse-temurin:217.9 s 232 MB9.2 s 225 MB2.9 s 49 MB1.0 s 0 MB
Kimi K2, 1 TBnot tried 1.03 TB17.6 s 433 MB3.4 s 56 MB1.2 s 0 MB

Every run but "again" starts from an empty cache. Bars replay at 3x speed.

A first run on a Mac, against Docker Desktop

CommandRangeDocker Desktop
pip install requests numpy12.2 s17.4 s
python -m compileall10.3 s14.6 s
cargo new, cargo build15.2 s16.7 s
go build, shared directory10.9 s15.1 s

Nothing downloaded yet, on a MacBook Pro with Apple silicon.

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.

docker pull downloads a whole image to run one command

python:3.12 is a 435 MB download. Starting Python needs 48 MB of it.

A model repository comes whole

unsloth/gemma-3-270m-it-GGUF holds 24 versions of one model in 6.38 GB. The demo needs one of them, 253 MB.

Fifty eval workers download the same thing fifty times

Each worker copies it to its own disk before it starts.

Range reads only the bytes each machine touches, and the next run fetches them before it asks.

design.txt

One abstraction, and nothing above it

ReadAt(offset, length) -> bytes. Range turns a source into a disk, and turns each read of that disk into a ranged request to the source.

An image becomes a disk

Range fetches each layer's index from a public catalog, or builds it once. After that a file costs one ranged request to the registry, and a few megabytes of gzip or zstd decompression at most. The image stays as it is.

A model repository becomes a disk

The file list comes from the Hugging Face API, pinned to one commit. Reading a file sends a ranged request to the Hub.

A shell is that, with a filesystem on top

disk -> read-only EROFS -> writable overlay -> namespaces. Writes stay local. Range checks every 64 KiB of an image layer against a SHA-256 in the index.

It learns the working set

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

install.txt

Install

A release archive for macOS or Linux, x86-64 or arm64. Then open a shell in any image:

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

For your own environments, build once, and every first run reads lazily:

$ range build --from-oci python:3.12 -o py.range
$ range publish py.range s3://<your-bucket>/py.range
$ range shell s3://<your-bucket>/py.range

A published artifact needs no indexing. The go1.23 demo artifact was ready in 0.41 s on its first run, and moved 6 MB of 1.03 GB.

Or build from source, with Go 1.25 or newer:

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

On Linux, Range needs QEMU and KVM, as root or in the kvm group. Without KVM, as root, it runs natively with the erofs and overlay kernel modules. A Mac with Apple silicon needs nothing more. An Intel Mac needs Lima: brew install lima. range doctor checks all of it. Windows works through WSL2, untested.

about_me.txt

A dithered black and white portrait of Andrey Grehov

I am a software engineer at AWS. Range is my personal project.

~/range $ view README.md