# How Container Manages Image Storage and Layers Locally: A Deep Dive into Apple's OCI Implementation

> Discover how Container manages local image storage and layers with its OCI compliant ContentStore, SnapshotStore, and ImageStore architecture. Learn Apple's implementation.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: deep-dive
- Published: 2026-06-11

---

**Container manages image storage and layers locally through a three-tier architecture comprising ContentStore for raw blobs, SnapshotStore for unpacked filesystems, and ImageStore for metadata orchestration, all following OCI specifications.**

The `apple/container` repository implements an OCI-compatible container runtime that stores images in a layered, content-addressable format. Understanding how container manages image storage and layers locally reveals a sophisticated system designed for deduplication, lazy loading, and efficient garbage collection. The implementation splits responsibilities across three cooperating Swift services that persist data to JSON-backed filesystem stores.

## The Three-Store Architecture

The storage layer is partitioned into specialized components, each handling a distinct aspect of image management. Separation of concerns allows for efficient deduplication and on-demand filesystem preparation.

### ContentStore: Raw Blob Storage

The **ContentStore** handles the raw binary objects that constitute an image. According to the source code in [`Sources/Services/ContainerImagesService/Server/ContentStoreService.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerImagesService/Server/ContentStoreService.swift), this store saves compressed layer tarballs, configuration JSON, and manifests under the `content` directory.

Each blob is addressed by its SHA-256 digest and written to `<root>/content/<digest>`, where `<root>` typically resolves to `~/.container`. The `save(blob:)` method ensures content-addressability by using the digest as the filename, guaranteeing that identical layers from different images share the same physical file.

### SnapshotStore: Filesystem Layer Management

The **SnapshotStore**, implemented in [`Sources/Services/ContainerImagesService/Server/SnapshotStore.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerImagesService/Server/SnapshotStore.swift), manages unpacked filesystem snapshots. When a layer is needed for execution, the store lazily extracts the corresponding blob into `<root>/snapshots/<digest>`.

This component tracks reference counts to determine when a snapshot can be safely removed. The `unpack(image:platform:)` method handles extraction, while `get(for:image:platform:)` retrieves existing snapshots or triggers unpacking on demand.

### ImageStore: Metadata Orchestration

The **ImageStore** resides in [`Sources/Services/ContainerImagesService/Server/ImagesService.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerImagesService/Server/ImagesService.swift) and maintains higher-level metadata. It records image manifests, tags, and references, mapping human-readable names like `docker.io/library/alpine:latest` to lists of layer digests.

Key operations include `save(references:out:platform:)` for recording new images and `delete(reference:performCleanup:)` for removal. The store persists manifest data under `<root>/images/<name>.json` using the `FilesystemEntityStore` generic persistence layer defined in [`Sources/ContainerPersistence/EntityStore.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/EntityStore.swift).

## The Image Pull Workflow

When executing `container image pull`, the system coordinates all three stores to populate local storage while maintaining OCI compliance.

1. **Fetch manifests and descriptors** – The CLI retrieves the manifest JSON from the remote registry, which contains a list of layer digests and the configuration digest.

2. **Store blobs** – Each layer digest is passed to `ContentStore.save(blob:)`, which writes the compressed tarball to the content directory using its digest as the filename.

3. **Create snapshots** – `SnapshotStore.unpack(image:platform:)` lazily extracts layers only when the image is run, creating directories under `<root>/snapshots/<digest>`. Reference counting ensures shared layers are tracked correctly.

4. **Record the image** – `ImageStore.save(references:out:platform:)` writes a JSON manifest that binds the image name to its constituent layer digests and configuration.

## Runtime Snapshot Management

During `container run`, the runtime requests the image manifest from `ImageStore`. For each layer digest in the manifest, the runtime calls `SnapshotStore.get(for:image:platform:)`. If the snapshot exists, it is reused; otherwise, the store fetches the blob from `ContentStore` and unpacks it.

The snapshots are mounted in order, with lower layers first and the top layer last, into the VM's rootfs. Because snapshots are immutable and content-addressed, they can be safely shared between concurrent containers, significantly reducing disk usage.

## Garbage Collection and Pruning

The `container image prune` command traverses `ImageStore` to identify unreferenced tags, then invokes `ImageStore.delete(reference:performCleanup:)`. This routine removes the manifest entry and notifies `ContentStore` to delete blobs whose reference counts drop to zero.

Orphaned snapshots are reclaimed by `SnapshotStore.clean(keepingSnapshotsFor:)`, which removes snapshot directories no longer associated with any stored image. The `EntityStore` persistence layer ensures all JSON writes are atomic and logged for debugging purposes.

## Content-Addressed Deduplication

Since all layers are content-addressed by digest, two different images sharing a base layer reference the exact same blob file in `<root>/content/` and the same snapshot directory in `<root>/snapshots/`. This automatic deduplication is enforced by reference-counting logic within the stores, requiring no additional CLI bookkeeping.

The `ImageResource` model defined in [`Sources/ContainerResource/Image/ImageResource.swift`](https://github.com/apple/container/blob/main/Sources/ContainerResource/Image/ImageResource.swift) encapsulates the relationship between names, tags, and digests, ensuring that storage operations remain consistent across the entire system.

## Summary

- **Three-tier storage** separates blob storage (`ContentStore`), filesystem snapshots (`SnapshotStore`), and metadata management (`ImageStore`) for clean separation of concerns.
- **Content-addressability** ensures layers are stored once under `<root>/content/<digest>` and shared across images via reference counting.
- **Lazy unpacking** defers layer extraction until runtime via `SnapshotStore.unpack()`, optimizing disk usage for pulled images that are never run.
- **Atomic persistence** is provided by `FilesystemEntityStore` in [`EntityStore.swift`](https://github.com/apple/container/blob/main/EntityStore.swift), which writes JSON files atomically with debug logging.
- **Automatic deduplication** occurs naturally because layers are identified by SHA-256 digest, eliminating redundant storage of shared base images.

## Frequently Asked Questions

### Where does Container store image layers on disk?

Container stores image layers under the `content` directory within its data root (typically `~/.container`). Each compressed layer blob is saved as a file named by its SHA-256 digest at `<root>/content/<digest>`. Unpacked filesystem snapshots reside in parallel under `<root>/snapshots/<digest>`, while image metadata JSON files are kept in `<root>/images/`.

### How does Container handle layer deduplication between different images?

Container achieves automatic deduplication through content-addressable storage. Since `ContentStore.save(blob:)` uses the SHA-256 digest as the filename, identical layers from different images write to the same path and are stored only once. The `SnapshotStore` maintains reference counts on unpacked layers, ensuring that shared layers remain available until all referencing images are deleted.

### What happens when I run `container image prune`?

The prune command triggers `ImageStore.delete(reference:performCleanup:)` to remove unreferenced manifests. It then notifies `ContentStore` to delete blobs with zero references. Finally, `SnapshotStore.clean(keepingSnapshotsFor:)` removes orphaned snapshot directories whose layers are no longer needed by any stored image, reclaiming disk space from extracted filesystems.

### Is the storage layout OCI-compatible?

Yes, the storage layout follows OCI image specifications. The `ContentStore` maintains blobs addressed by digest, while `ImageStore` preserves manifests and configuration JSON in a structure that mirrors the OCI image layout. This design allows for interoperability with standard OCI tools and registries.