Problem summary
When a small source change makes a Docker build re-run dependency installs, many developers say “the cache didn’t work.” The clearer diagnosis is that Docker has at least two different cache mechanisms: instruction/layer cache (which can skip an instruction entirely) and cache mounts (which provide a persistent directory to a step when that step runs). They solve different problems and have different guarantees.
Implementation approach
This article keeps the implementation narrow and verifiable: create a helper that validates the fields Docker documents for RUN --mount=type=cache, emits the mount declaration exactly, and uses a sensible default identity. Concretely, the helper verifies that the mount target is a non-empty absolute path composed of safe characters, that an explicit id (if provided) is a non-empty safe string, and that sharing is one of the documented values (shared, private, locked). The helper then returns the canonical shape for the mount options and formats the --mount fragment you will paste into a Dockerfile instruction.
Expected behavior
- Default id equals the mount target path. That mirrors Docker's documented default and gives a stable, readable identity in multi-stage builds.
- When the install step runs, the mount provides the package manager's actual cache directory so downloads can be reused if the builder still holds the storage.
- Choose sharing=locked when a tool expects exclusive access to the cache files (helps with file-locking semantics on CI with concurrent builds).
Limitations and practical notes
- A correct mount declaration does not force persistence across fresh builder instances. Docker stores cache mounts in the builder’s local storage; if your CI creates a fresh builder each job or the builder garbage-collects storage, the mount will start empty.
- Cache mounts make reruns cheaper by preserving downloaded artifacts, but they do not change Docker’s invalidation rules. If a dependency manifest changes, the install step should run again; the mount only reduces the download cost during that rerun.
- The helper intentionally restricts allowable characters in target and id to avoid injection into the flag syntax. Projects that need broader naming schemes should provide a separate, reviewed configuration path.
Why this framing helps
By separating what the Dockerfile can guarantee (a valid, stable mount declaration) from what the environment must provide (builder-local persistence), you get a predictable place to reason about performance: fix the declaration first, then address your CI/builder topology if persistence is still missing.
(The server will insert the verified implementation example as a fenced code block below this explanation.)
Working example
const SHARING_VALUES = new Set(['shared', 'private', 'locked']);
const SAFE_COMPONENT = /^[A-Za-z0-9_./-]+$/;
export function validateCacheMountInput({ target, id, sharing = 'shared' } = {}) {
if (typeof target !== 'string' || target.length === 0) {
throw new TypeError('target must be a non-empty string');
}
if (!target.startsWith('/')) {
throw new RangeError('target must be an absolute path');
}
if (!SAFE_COMPONENT.test(target)) {
throw new RangeError('target must contain only letters, digits, slash, dot, underscore or hyphen');
}
if (id !== undefined) {
if (typeof id !== 'string' || id.length === 0) {
throw new TypeError('id must be a non-empty string when provided');
}
if (!SAFE_COMPONENT.test(id)) {
throw new RangeError('id must contain only letters, digits, slash, dot, underscore or hyphen');
}
}
if (!SHARING_VALUES.has(sharing)) {
throw new RangeError('sharing must be one of shared, private, locked');
}
}
export function buildCacheMountOptions({ target, id, sharing = 'shared' } = {}) {
validateCacheMountInput({ target, id, sharing });
return { type: 'cache', target, id: id ?? target, sharing };
}
export function formatRunMount(options) {
const { type, target, id, sharing } = buildCacheMountOptions(options);
return `--mount=type=${type},target=${target},id=${id},sharing=${sharing}`;
}
Read the full article: https://chriseugenerodriguez.com/blog/reuse-cache-mounts-multi-stage-dockerfiles
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.