cargo-overlay-registry
A read-write overlay layer for cargo registries. Like an overlay filesystem, it provides a writable local layer on top of a read-only upstream registry (e.g., crates.io). Publishes go to the local layer, while reads fall through to the upstream when not found locally.
How It Works
flowchart TB
cargo["cargo build/publish"]
subgraph proxy["cargo-overlay-registry"]
direction TB
local["Local Registry (writable)"]
tmp["tmp-registry (read-only)"]
crates["crates.io (read-only)"]
local -->|fallback| tmp
tmp -->|fallback| crates
end
cargo -->|HTTP_PROXY| proxy
-
Local Registry: Receives publishes (
-r local) -
tmp-registry: Temp registry created by
cargo publish(-r local=./target/package/tmp-registry) -
crates.io: Upstream fallback (
-r crates.io) -
Publish: Crates go to the top-most layer (local or remote with forwarding)
-
Download: Local crates are served first; missing crates fall through to the next layer
-
Index: Indexes from all layers are merged from top to bottom; top layers win conflicts
Multiple registry layers can be stacked using the -r flag.
Use Case: Dry-Run Publishing
Until recently, cargo publish --workspace was unstable. Now it's stable — but it still breaks if any crate has a build script that invokes cargo (e.g., to run cargo metadata or build another crate). The inner cargo process tries to resolve workspace dependencies that haven't been published yet.
The overlay solves this by capturing publishes locally while still resolving real dependencies from upstream. Build scripts see all workspace crates as if they were already published.
Installation
Quick Start
1. Start the overlay
2. Configure cargo
Set the environment variables shown in the server output:
3. Use cargo normally
# Builds use local crates + upstream fallback
# Publishes go to the local overlay
Quick Publish Test
Use -- to run a command with the proxy already configured:
# Test publishing a single crate
# Publish a workspace
The proxy starts, sets CARGO_HTTP_PROXY, CARGO_HTTP_CAINFO, and CARGO_REGISTRY_TOKEN for the child process, runs the command, and exits with its exit code.
Metadata validation is enforced by default (same as crates.io), so you can verify your crate meets publishing requirements before actually publishing. Use --permissive-publishing to skip validation.
Build Scripts That Invoke Cargo
When publishing a workspace, cargo publish packages each crate to <target>/package/tmp-registry before building and validating. If a crate has a build script that invokes cargo (e.g., to build another crate or run cargo metadata), that inner cargo process needs to resolve dependencies — including workspace crates that were just packaged.
Add a local registry layer pointing to the tmp-registry:
This ensures build scripts can resolve workspace crates that have been packaged but not yet published.
Options
| Option | Short | Default | Description |
|---|---|---|---|
--port |
-p |
8080 | Server port (registry + proxy) |
--host |
-H |
0.0.0.0 | Host to bind to |
--base-url |
-b |
https://crates.io | Base URL for the proxy (used in config.json) |
--registry |
-r |
local + crates.io |
Registry layers (see below) |
--no-proxy |
Disable proxy mode (CONNECT handling with MITM) | ||
--read-only |
Make the registry read-only (reject all publish requests) | ||
--ca-cert-out |
(temp file) | Export CA certificate for HTTPS interception | |
--permissive-publishing |
Skip crates.io metadata validation | ||
--no-tls |
Disable HTTPS (use plain HTTP) | ||
--tls-cert |
TLS certificate file (PEM) | ||
--tls-key |
TLS private key file (PEM) |
Registry Layers (-r)
Registry layers are stacked top-to-bottom. The topmost registry receives publishes (unless --read-only is set); reads check each layer in order. Remote registries forward publish requests to the upstream with the provided auth token.
| Syntax | Description |
|---|---|
-r local |
Local registry in a temp directory |
-r local=/path |
Local registry at the specified path |
-r crates.io |
Shortcut for crates.io remote |
-r remote=URL |
Custom remote registry (URL used for both API and index) |
-r remote=API,INDEX |
Custom remote with separate API and index URLs |
Examples:
# Default: local temp dir + crates.io
# Local registry at specific path + crates.io
# Stack multiple layers: writable local, shared local, crates.io
# Read-only mode (no publishing allowed)
Example: Publishing Dependent Crates
# Start the overlay
# In another terminal, configure cargo (paths shown in server output)
# Publish my-core (stored locally, not on crates.io)
# Publish my-app which depends on my-core
# The overlay serves my-core from the local layer
# Build a project that uses my-app
# The overlay serves my-app and my-core locally,
# fetches other dependencies from crates.io
Technical Details
MITM TLS Interception
The overlay intercepts HTTPS traffic to crates.io domains using MITM TLS:
- Generates certificates on-the-fly signed by the overlay's CA
- Non-registry traffic passes through unmodified
- Use
CARGO_HTTP_CAINFOto trust the CA certificate
Storage Layout
{registry-path}/
├── {name}-{version}.crate # Published .crate files (flat)
└── index/{prefix}/{name} # Index entries (JSON lines)
The prefix follows cargo's sparse index convention: 1/ for single-char names, 2/ for two-char, 3/{first-char}/ for three-char, and {first-two}/{second-two}/ for longer names.
Registry API
Implements the Cargo Registry HTTP API:
GET /config.json— Registry configurationGET /{first-two}/{second-two}/{crate}— Index lookup (merged with upstream)GET /api/v1/crates/{name}/{version}/download— Crate download (local-first)PUT /api/v1/crates/new— Publish (stored locally)
License
Apache-2.0