Skip to content

alltuner/switchyard

Repository files navigation

Switchyard

Local Docker registry with async upstream sync
Push images at local disk speed, sync to your central registry in the background.

GitHub · Sponsor

License Stars


How it works

docker push localhost:5050/myapp:latest
        │
        ▼
   ┌─────────┐       background sync     ┌─────────────────┐
   │Switchyard│  ─────────────────────►   │ Central Registry │
   │ (local)  │                           │ (self-hosted)    │
   │ :5050    │  ◄── reverse proxy ─────  │                  │
   └─────────┘    (pulls, cached)         └─────────────────┘
  1. Push lands on your local machine instantly
  2. Background worker syncs blobs and manifests to the central registry with retry and backoff
  3. Pull checks local storage first, proxies to upstream on miss and caches the result
  4. Layer deduplication: blobs that already exist upstream are never re-pushed

Quick start

Docker Compose (recommended)

Copy compose.example.yaml to compose.yaml and fill in your upstream registry URL:

services:
  switchyard:
    image: ghcr.io/alltuner/switchyard:latest
    ports:
      - "5050:5050"
    volumes:
      - switchyard-data:/data
    environment:
      SWITCHYARD_UPSTREAM: "https://your-central-registry:5000"
    restart: unless-stopped

volumes:
  switchyard-data:
docker compose up -d

From source

uv sync
uv run switchyard

Docker daemon config

Add localhost:5050 as an insecure registry in ~/.docker/daemon.json:

{
  "insecure-registries": ["localhost:5050"]
}

Restart Docker Desktop (or the daemon) after making this change.

Push an image

docker tag alpine:latest localhost:5050/alpine:latest
docker push localhost:5050/alpine:latest

Configuration

All configuration is via environment variables with the SWITCHYARD_ prefix:

Variable Default Description
SWITCHYARD_DATA_DIR ./data Directory for blobs, manifests, and sync queue
SWITCHYARD_UPSTREAM (empty) Central registry URL. Empty = local-only mode
SWITCHYARD_SYNC_INTERVAL 10 Seconds between sync queue scans
SWITCHYARD_MANIFEST_TTL 300 Seconds before re-fetching a cached tag manifest from upstream

Sync queue

Pending syncs are stored as JSON files in $SWITCHYARD_DATA_DIR/pending/. You can inspect them with ls:

ls data/pending/
# myapp/latest.json

Failed syncs retry with exponential backoff (5s, 10s, 20s, ... capped at 5 minutes). Delete a marker file to cancel a pending sync.

Development

uv sync --all-groups
uv run pytest -v
uv run ruff check src/ tests/

Tech stack

  • Python 3.14+, async throughout
  • Starlette for the HTTP layer
  • Granian (Rust ASGI server) with uvloop
  • httpx for upstream communication (HTTP/2, brotli, zstd)
  • File-based storage and sync queue (no database)

Support the project

Switchyard is an open source project built by David Poblador i Garcia through All Tuner Labs.

If this project is useful to you, consider supporting its development.

❤️ Sponsor development https://github.com/sponsors/alltuner

One-time support https://buymeacoffee.com/alltuner

Your support helps fund the continued development of Switchyard and other open source developer tools such as Factory Floor.

License

MIT


Built by David Poblador i Garcia with the support of All Tuner Labs.
Made with ❤️ in Poblenou, Barcelona.

About

Local Docker registry with async upstream sync

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

 
 
 

Contributors