GitLab CI Caching Strategies: A Deep Dive That Actually Speeds Up Your Pipeline
Cache keys, policies, fallback keys and the artifacts-vs-cache distinction — a practical deep dive into GitLab CI caching that turns slow pipelines fast.
- #gitlab
- #cicd
- #caching
- #performance
- #pipeline-optimization
- #runners
The single biggest pipeline speedup I’ve ever shipped wasn’t a faster runner or a clever parallelization scheme. It was fixing the cache. The team had a “cache” block that looked correct, restored nothing useful, and re-downloaded the entire dependency tree on every single job. Once the cache actually worked, a 14-minute pipeline became a 5-minute one. No new infrastructure — just understanding how GitLab caching actually behaves.
Caching in GitLab is one of those features that looks simple and has a dozen sharp edges. This is the deep dive that covers the edges.
Cache vs. artifacts: get this right first
Almost every caching mistake starts with confusing these two:
- Cache is for things that speed up a job but aren’t part of the build output — downloaded dependencies, compiler caches. It’s keyed, best-effort, and may be stale or missing. A job must still work if the cache is empty.
- Artifacts are outputs you produce and pass to later stages — compiled binaries, test reports, the built image manifest. They’re guaranteed to be there for downstream jobs.
If you use cache to pass build output between stages, you’ll get intermittent failures when the cache misses. If you use artifacts to store node_modules, you’ll bloat storage and slow uploads. Rule of thumb: cache your inputs, artifact your outputs.
The cache key is everything
A cache is only useful if jobs share the right key. The naive cache.key is static, which means every branch shares one cache and they clobber each other. The right pattern keys on your lockfile:
cache:
key:
files:
- package-lock.json
paths:
- .npm/
Now the cache key is a hash of package-lock.json. Change a dependency, the lockfile changes, the key changes, you get a fresh cache. Don’t change deps, every job restores the same warm cache. This is the single most important caching pattern in GitLab CI.
For tools with their own cache directory, point the tool at a cached path:
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
cache:
key:
files:
- package-lock.json
prefix: "$CI_JOB_NAME"
paths:
- .npm/
The prefix lets different job types (lint vs. build) keep separate caches keyed on the same lockfile.
Cache policies: pull, push, or both
By default a job both restores the cache at the start and uploads it at the end. That upload costs time. If a job only reads the cache and never changes it, tell GitLab not to bother re-uploading:
install_deps:
stage: prepare
cache:
key:
files: [package-lock.json]
paths: [.npm/]
policy: pull-push # populate the cache once
test:
stage: test
cache:
key:
files: [package-lock.json]
paths: [.npm/]
policy: pull # read-only, no upload
One job populates the cache (pull-push); every downstream job that just consumes it uses pull. On a pipeline with ten test jobs, that’s nine cache uploads you just eliminated.
Fallback keys: warm cache on the first run
The lockfile-keyed cache has a cold-start problem: the first pipeline after a dependency change has no matching cache and downloads everything. Fallback keys soften that:
cache:
key:
files: [package-lock.json]
fallback_keys:
- "default-cache"
paths:
- .npm/
If the exact key misses, GitLab restores the fallback — usually 90% of what you need, so you download only the few changed packages instead of all of them. Pair this with a job on your default branch that maintains the default-cache key, and feature branches start warm.
Where the cache actually lives matters
Here’s the gotcha that bites people on Kubernetes runners: by default, cache is stored on the runner’s local disk. With autoscaling or ephemeral Kubernetes runners, the next job lands on a different runner with an empty disk — your cache effectively never hits. The fix is distributed caching backed by object storage (S3, GCS):
# runner config.toml
[runners.cache]
Type = "s3"
Shared = true
[runners.cache.s3]
ServerAddress = "s3.amazonaws.com"
BucketName = "gitlab-runner-cache"
With shared object-storage caching, any runner can restore any cache. On ephemeral runner fleets this is the difference between caching working and caching being theater. If your caches “aren’t helping” on Kubernetes, check this first.
A debugging checklist
When a cache isn’t helping, walk this list:
- Is it a hit or a miss? The job log says
Successfully extracted cache(hit) orNo URL provided, cache will not be downloaded(misconfig). Read it. - Is the key stable? If your key includes
$CI_COMMIT_SHA, every job has a unique key and nothing ever hits. Key on lockfiles, not commits. - Is the path right? Cache paths must be inside
$CI_PROJECT_DIR. Anode_modulesoutside the project dir won’t be cached. - Distributed cache configured? On ephemeral runners, no object storage means no shared cache.
This is the kind of grind where an AI assistant earns its keep — paste your .gitlab-ci.yml cache config and a job log showing the miss, and ask it to explain why the key isn’t matching. It’s faster than re-reading the docs for the fifth time. Our AI Code Review assistant catches a surprising number of “this cache will never hit” config mistakes in pipeline diffs.
The payoff
Get caching right and you compound the win: faster pipelines mean faster feedback, which means developers actually wait for the pipeline instead of context-switching away. Cache your inputs, key on lockfiles, use pull policy for read-only jobs, set fallback keys, and put object storage behind ephemeral runners. For more on the runner side of this, see our GitLab CI/CD guides.
Configuration advice and AI-generated cache diagnostics are assistive. Verify cache behavior against your own runner setup and job logs before relying on it.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.