# Content-Addressable Storage in uv’s Cache: How It Works

> Discover content-addressable storage in uv's cache. Learn how SHA-256 hashes deduplicate, ensure immutability, and verify Python package downloads for faster, reliable installations.

- Repository: [Astral/uv](https://github.com/astral-sh/uv)
- Tags: internals
- Published: 2026-03-01

---

**uv stores every downloaded Python package in a content-addressable cache where each file is identified solely by its SHA-256 hash, ensuring that identical artifacts are deduplicated, immutable, and cryptographically verifiable on every read.**

Content-addressable storage (CAS) is the architectural foundation of uv’s high-performance caching system. In the astral-sh/uv repository, wheels, source tarballs, and metadata files are persisted under paths derived from their cryptographic hashes rather than their URLs or package names. This design eliminates redundant downloads across projects, prevents cache corruption, and enables safe, granular pruning without orphaning shared dependencies.

## What Is Content-Addressable Storage in uv?

In uv’s implementation, content-addressable storage means that the **filename and path of a cached artifact are deterministically derived from the SHA-256 hash of its contents**. When uv downloads a wheel from PyPI or a private index, it computes the hash of the downloaded bytes and stores the file at a location calculated from that hash. Consequently, two identical wheels fetched from different indexes—or at different times—map to the exact same on-disk path, ensuring only one copy is ever retained.

### Hash-Based File Layout

The physical layout is implemented in [`crates/uv-fs/src/cachedir.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-fs/src/cachedir.rs). To avoid placing thousands of files in a single directory, uv uses a two-level hierarchy:

```

<cache_root>/<2-char hash prefix>/<full 64-char hash>

```

This structure keeps directory listings short while providing O(1) lookups. The full hash string serves as both the filename and the integrity checksum, making the storage self-describing.

## Why uv Uses Content-Addressable Storage

uv leverages content-addressable storage to solve five specific challenges in Python package management:

- **Deduplication** – Identical wheels from multiple indexes (PyPI, private registries, or local builds) are stored only once. Because the hash guarantees byte-for-byte equality, uv can safely reuse the same file for every project that depends on it, regardless of the source URL.

- **Immutability** – A cached file never changes once written because its name is the hash of the original bytes. This eliminates subtle bugs caused by partial downloads or corrupted overwrites; if the file exists at the expected hash path, its contents are guaranteed to match.

- **Cache-Wide Integrity** – When uv reads a cached artifact, it recomputes the SHA-256 hash and verifies it matches the filename. Corrupted or tampered files are rejected and re-downloaded automatically, providing cryptographic assurance of package integrity.

- **Fast Lookups** – Computing the hash of a request’s URL plus environment parameters (Python version, platform tags, etc.) yields a deterministic *cache key*. This key maps directly to a file path, avoiding expensive directory scans or database queries.

- **Safe Pruning** – Because each entry is self-contained and named by hash, uv can safely delete least-recently-used entries without orphaning files shared by multiple projects. The `uv cache prune` command relies on this property to reclaim space precisely.

## How uv Implements Content-Addressable Storage

### Cache Key Generation

The module [`crates/uv-cache-key/src/cache_key.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-cache-key/src/cache_key.rs) defines how uv constructs deterministic identifiers for cache entries. For a wheel, the cache key incorporates the package name, version, Python tag, ABI tag, and platform tag. This key is then hashed to a 64-bit value that serves as the filename within the cache directory. The deterministic nature of this key ensures that identical resolution requests always map to the same storage location.

### Distribution and Validation

When loading a wheel, [`crates/uv-distribution/src/index/cached_wheel.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-distribution/src/index/cached_wheel.rs) reconstructs the expected path from the cache key and attempts to open the file. If the file exists, uv validates integrity by recomputing the SHA-256 hash of the file contents and comparing it against the filename. Any mismatch triggers a cache invalidation and re-download, ensuring that only verified artifacts are installed.

### Cache Lifecycle and Pruning

The `uv cache prune` command, implemented in [`crates/uv/src/commands/cache_prune.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv/src/commands/cache_prune.rs), walks the two-level hash directory and removes entries whose last-access time exceeds the specified threshold. Because files are content-addressed, pruning is strictly safe—removing a file never breaks another project that might have referenced the same artifact, since the artifact would simply be re-downloaded to the same hash path if needed again. The `uv cache clean` command ([`cache_clean.rs`](https://github.com/astral-sh/uv/blob/main/cache_clean.rs)) provides a nuclear option to remove all cached artifacts entirely.

## Working with uv’s Content-Addressable Cache

### Inspecting Cached Artifacts

You can locate a specific wheel in the cache by computing its expected path. While uv does not expose a direct "show cache path" command, understanding the layout helps debug cache hits:

```bash

# Default cache location

ls ~/.cache/uv

# List a specific hash directory (example hash prefix)

ls ~/.cache/uv/ab/

```

### Pruning Old Entries

To reclaim disk space without clearing the entire cache, use the prune command with a maximum age:

```bash

# Display current cache size

uv cache size

# Remove entries not accessed in the last 30 days

uv cache prune --max-age 30d

```

This command safely targets specific hash-named files in [`crates/uv/src/commands/cache_prune.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv/src/commands/cache_prune.rs), ensuring no cross-project contamination.

### Configuring a Custom Cache Directory

You can redirect the entire content-addressable store to a different filesystem or disk by setting the environment variable:

```bash

# Use a custom cache root

UV_CACHE_DIR=/mnt/fastdisk/uv-cache uv install .

```

`uv` reads this variable during startup ([`uv-fs/src/cachedir.rs`](https://github.com/astral-sh/uv/blob/main/uv-fs/src/cachedir.rs)) and maintains the same `<prefix>/<hash>` layout under the new root.

## Summary

- **Content-addressable storage** in uv means every cached file is stored at a path derived from its SHA-256 hash, making the cache self-describing and immutable.
- **Deduplication** occurs automatically because identical wheels from any source map to the same hash path, eliminating redundant storage.
- **Integrity verification** happens on every read by recomputing the hash and comparing it to the filename, ensuring corrupted artifacts are never installed.
- **Safe pruning** is enabled by the hash-based layout; commands like `uv cache prune` remove specific least-recently-used entries without orphaning shared dependencies.
- **Key implementation files** include [`crates/uv-cache-key/src/cache_key.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-cache-key/src/cache_key.rs) for key generation and [`crates/uv-fs/src/cachedir.rs`](https://github.com/astral-sh/uv/blob/main/crates/uv-fs/src/cachedir.rs) for the two-level directory layout.

## Frequently Asked Questions

### How does uv handle hash collisions in its content-addressable storage?

uv relies on SHA-256 for content addressing, which provides 256 bits of collision resistance. The probability of an accidental collision is astronomically low—effectively zero for practical purposes—so uv uses the full 64-character hex hash as the filename. If a collision were to occur, the files would have identical contents anyway, so no data loss or corruption would result.

### Can I safely delete files directly from uv’s cache directory?

Yes. Because uv uses content-addressable storage, each file is immutable and self-contained. Deleting a specific hash-named file from the `<cache_root>/<prefix>/<hash>` path only removes that exact artifact. If another project needs the same dependency later, uv will simply re-download it to the same hash path. However, using `uv cache prune` or `uv cache clean` is safer than manual deletion because it ensures internal state remains consistent.

### How does uv verify cache integrity without slowing down installations?

When uv reads a cached artifact, it optionally recomputes the SHA-256 hash of the file contents and compares it against the filename (which is the expected hash). This operation is fast because it involves a single sequential read of the file, and modern SHA-256 implementations are highly optimized. If the hash matches, the file is used; if not, uv treats it as a cache miss and re-downloads the artifact, ensuring that corrupted or tampered files are never installed.

### What happens if the uv cache disk fills up?

If the cache disk reaches capacity, uv will fail to write new artifacts, potentially causing installation errors. To prevent this, monitor cache size with `uv cache size` and prune old entries using `uv cache prune --max-age <duration>` (e.g., `30d` for 30 days). Because the cache is content-addressable, pruning safely removes only unreferenced old versions without risking the integrity of active projects. For immediate relief, you can also move the cache to a larger disk by setting the `UV_CACHE_DIR` environment variable.