Understanding the Caching Mechanism Employed by Container for Builds: cache-in and cache-out Explained

The apple/container build system implements a persistent build cache using --cache-in and --cache-out flags to import and export cache layers, enabling reproducible and accelerated builds across machines, CI pipelines, and developer workstations.

The apple/container repository provides a sophisticated build caching mechanism built on top of BuildKit-style cache-to and cache-from semantics. This system allows developers to attach external cache sources at build initiation and persist generated cache layers to external locations upon completion. By leveraging explicit cache import and export operations, the tool minimizes redundant computation while maintaining build reproducibility across distributed environments.

How Cache-In Imports External Cache Sources

The --cache-in flag enables the build daemon to consume pre-existing cache artifacts from local or remote locations. When you specify one or more cache sources, the system treats these as read-only inputs available to any build step that supports cache mounting.

Implementation in Builder.swift

In Sources/ContainerBuild/Builder.swift, the build request construction process iterates over the config.cacheIn array and attaches each value as a metadata entry. The daemon then processes these entries as cache sources that can satisfy RUN --mount=type=cache directives or layer reuse optimizations.

// Inside Builder.swift – attaching cache-in metadata
for cacheIn in config.cacheIn {
    metadata.addString(cacheIn, forKey: "cache-in")
}

Cache sources can include local directories, previously exported tar archives, or remote URLs pointing to OCI registries. The build daemon validates accessibility of these sources before attempting to resolve cached layers.

How Cache-Out Exports Build Cache

The --cache-out flag operates as the inverse of --cache-in, capturing the build cache generated during the current execution and writing it to specified destinations. This export functionality is essential for CI workflows where subsequent builds or parallel jobs need access to warmed caches.

Persistence and Storage Formats

Each --cache-out argument is attached to the build request as a cache-out metadata entry in Builder.swift. After the build completes successfully, the daemon serializes the cache layers to the specified locations, which can be local tarballs or remote OCI-compliant registries.

// Inside Builder.swift – attaching cache-out metadata
for cacheOut in config.cacheOut {
    metadata.addString(cacheOut, forKey: "cache-out")
}

The exported cache preserves layer relationships and build metadata, ensuring that imported caches maintain the same fidelity when reused in future builds.

Filesystem Cache Mode Configuration

Beyond import/export semantics, the caching mechanism controls filesystem-level caching behavior through the CacheMode enum defined in Sources/Container/Filesystem.swift.

CacheMode Enum and Default Behavior

Every mount—whether a block device or volume—can operate in either .on (cached) or .off (uncached) mode. The default value is .on, which enables the build-time cache layer and minimizes disk I/O while preserving correctness.

// Filesystem.swift – default cache mode for mounts
public enum CacheMode: String, Codable {
    case on   // "cached" – the default; uses the build-time cache layer
    case off  // "uncached" – disables the cache for this mount
}

When CacheMode is set to .off, the build system bypasses the caching layer for that specific mount, performing direct I/O operations instead. This is useful for scenarios where cache coherence must be strictly enforced or when working with volatile data that should not be cached.

Practical Usage Examples

Local Cache Archives

Import a previously exported cache archive to warm the build, then export the resulting cache for future use:


# Import a previously-exported cache archive

container build \
    --cache-in ~/my-cache.tar \
    -t my-app:latest .

# Export the cache generated by the current build

container build \
    --cache-out ~/new-cache.tar \
    -t my-app:latest .

Remote OCI Registries

For CI pipelines, use a remote OCI registry as both the cache source and destination, enabling shared caches across build agents:


# Use a remote OCI cache as both input and output

container build \
    --cache-in oci://ghcr.io/example/my-cache:latest \
    --cache-out oci://ghcr.io/example/my-cache:latest \
    -t my-app:ci .

This approach ensures that the first build populates the registry cache, while subsequent builds—even on different machines—can immediately leverage the warmed layers without rebuilding intermediate steps.

Summary

  • Cache-in (--cache-in) imports external cache sources by attaching them as metadata entries in Builder.swift, making them available to build steps that read from the cache.
  • Cache-out (--cache-out) exports generated cache layers to specified destinations after build completion, facilitating cache reuse across machines and CI runs.
  • Filesystem caching uses the CacheMode enum in Filesystem.swift, defaulting to .on for optimal performance while allowing explicit uncached mounts when needed.
  • Supported formats include local directories, tar archives, and remote OCI registries, providing flexibility for both local development and cloud-based workflows.

Frequently Asked Questions

What is the difference between cache-in and cache-out in container builds?

Cache-in specifies external cache sources to import at the start of a build, while cache-out defines destinations where the build system should export generated cache layers after completion. You can use both flags simultaneously to update a shared cache while benefiting from previous cached state.

Where does the container build system store cache metadata?

The container build system stores cache metadata temporarily during the build process in Sources/ContainerBuild/Builder.swift, where it attaches cache-in and cache-out values to the gRPC build request. Persistent storage locations depend on your --cache-out arguments, which can specify local paths or remote OCI registries.

Can I disable caching for specific mounts in a container build?

Yes, you can disable caching for specific mounts by setting the CacheMode to .off in Sources/Container/Filesystem.swift. By default, mounts use .on mode, which enables the caching layer, but you can configure individual mounts to perform uncached I/O operations when cache coherence is critical.

What cache formats does the apple/container build system support?

The build system supports local directories, tar archives (.tar files), and remote OCI registry URLs (using the oci:// scheme). This flexibility allows you to share caches across developer workstations via network drives or centralize them in container registries for CI/CD pipelines.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →