Caching and Artifacts
This domain tests the distinction between cache and artifacts, how cache keys select storage, and how artifacts travel between jobs.
The core distinction
- Cache is a performance optimisation stored on the runner's cache backend. There is no guarantee it exists; treat every cache as potentially cold.
- Artifacts are produced by a job, uploaded to GitLab, and guaranteed to be downloadable by later jobs and by users, subject to
expire_in(default 30 days).
Caching node_modules or .m2 speeds up builds. Using cache to hand a compiled binary to a deploy job is wrong - that is what artifacts are for.
Cache keys
cache:key selects the bucket a job reads and writes. A static string shares one bucket across pipelines; $CI_COMMIT_REF_SLUG gives a per-branch bucket. cache:key:files hashes the contents of up to two files, commonly lockfiles, so the cache invalidates when dependencies change. cache:key:prefix prepends a string to a files-derived key, combining branch isolation with lockfile invalidation. cache:policy is pull-push (default), pull, or push; cache:fallback_keys lets a job try several keys in order.
Artifacts
artifacts:paths, artifacts:exclude, artifacts:name, artifacts:when (on_success default, on_failure, always), and artifacts:reports (JUnit, coverage, SAST, dependency scanning), which GitLab parses rather than merely stores.
By default a job downloads artifacts from all jobs in earlier stages. Once you use needs, it downloads only from the jobs it lists. dependencies: [] disables artifact download entirely; needs: [{job: build, artifacts: true}] is the explicit form.
Worked example
build:
script: npm ci && npm run build
cache:
key:
files: [package-lock.json]
prefix: $CI_COMMIT_REF_SLUG
paths: [node_modules/]
policy: pull-push
artifacts:
paths: [dist/]
expire_in: 1 week
Reasoning: the cache key changes when package-lock.json changes or the branch changes, so stale node_modules are not reused. The artifact is what a later deploy job consumes, and it disappears after a week. If a second job only reads the cache, give it policy: pull so it does not re-upload an identical archive.
Traps
- Assuming cache is shared between runners or projects - it is scoped to the runner's backend and the key.
- Pointing
cache:key:filesat a file that does not exist in the repository. - Forgetting that
needsnarrows artifact downloads, so a job silently loses a file it used to receive. - Setting
expire_inshorter than the interval a downstream scheduled job needs the artifact.