Pull-through cache for Docker images

Pull once.
Never twice.

Stashito sits between your machines and your upstream registries — Docker Hub, GHCR, Quay, anything OCI. The first pull fetches an image from upstream and keeps every layer on disk — every pull after that is served from your own network. No rate limits, no wasted bandwidth, no waiting on the internet.

free · source-available · single Go binary · ☕ buy me a coffee

Why bother

The same layers, over and over

Every CI job, every homelab node, every rebuilt VM pulls the same base images from the same registry across the same internet connection.

Rate limits stop hurting

Docker Hub caps anonymous pulls. Behind Stashito, upstream sees one pull per image — your fleet can hammer the cache all day.

LAN speed, not WAN speed

Cached layers come off local disk over your own network. Pulling a ~150 MB image takes seconds, not minutes.

Survives a dead internet

Images you've pulled before keep working when your uplink — or the registry — doesn't. Deploys don't wait on someone else's outage.

How it works

A registry that remembers

Stashito speaks the OCI Distribution Spec — the same HTTP API every registry speaks. Docker talks to Stashito like any registry; Stashito only talks to upstream when it has to.

Your machines

docker, CI runners, k8s nodes — anything that pulls images.

Stashito

Checks its filesystem cache for the manifest and each blob. Hits are served straight from disk.

Upstream registry

Docker Hub, GHCR, Quay, GAR, ACR — sees each manifest and blob once. Then never again.

Run it

Two minutes to a warm cache

1 start stashito

# docker-compose.yml
services:
  stashito:
    image: stashito
    environment:
      PORT: "8080"
      STORAGE_PATH: "/data/stashito"
      LOG_LEVEL: "info"
      TAG_TTL: "60s"
      UPSTREAM_DOCKERHUB_HOST: "registry-1.docker.io"
    volumes:
      - stashito_data:/data/stashito
    ports:
      - "8080:8080"

volumes:
  stashito_data:

2 pull through it

# prefix the image path with the upstream
docker pull localhost:8080/dockerhub/library/postgres:16
docker pull localhost:8080/dockerhub/library/redis:7

The first segment names an upstream you configured — dockerhub here maps to UPSTREAM_DOCKERHUB_HOST. Everything after it is the ordinary image path. First pull fills the cache; every later pull, from any machine that can reach Stashito, is local.

Config

A handful of variables. That's it.

PORT
HTTP port Stashito listens on.
STORAGE_PATH
Directory where cached manifests and blobs live. Point it at a volume you're happy to let grow.
LOG_LEVEL
debug, info, warn or error.
TAG_TTL
How long a cached tag is served without checking upstream for a newer digest. Go duration, e.g. 60s. Digests and blobs are immutable and never revalidated.
UPSTREAM_<ALIAS>_HOST
One per upstream registry. The lowercased alias becomes the image-path prefix. Add _USERNAME / _PASSWORD for private registries — GHCR, Quay, GAR and ACR all work.

Everything is explicit — Stashito never guesses defaults for you.