Cache GitLab CI dependencies without rebuilding every pipeline
Control dependency reuse in GitLab CI/CD with cache:key, paths, and policy so feature-branch pipelines install faster without stale modules.
20 Jun 2026, 23:40 UTC

Slow feature-branch pipelines
Every time you push to a feature branch, GitLab CI runs npm install from scratch. The job downloads hundreds of packages, the test stage waits several minutes, and the result is identical to the previous run. That repetition wastes compute time and delays feedback.
The useful takeaway: GitLab CI/CD can store a dependency directory between jobs and across pipelines using a cache archive. You control what is stored with cache:paths, who can reuse it with cache:key, and whether a job downloads, uploads, or both with cache:policy. A cache is not a build artifact and is not guaranteed to persist forever, but for dependency directories it can cut install time significantly.
How cache scope works
A GitLab CI cache is a compressed archive of the directories or files you list in cache:paths. GitLab associates the archive with a cache key. When a later job runs with the same key, it downloads the archive before the script starts. When the job finishes, it can upload an updated archive.
cache:key determines the reuse scope. If two jobs share a key, they share the cache. If the key changes on every commit, no job can reuse another job's upload. A key built from CI_COMMIT_REF_SLUG keeps caches separate by branch while letting every commit on that branch reuse the same archive.
cache:policy accepts three values:
pull-push(default): download the cache at the start, upload changes at the end.pull: download only. Useful for jobs that consume dependencies but never modify them.push: upload only. Useful for a dedicated job that prepares a cache for downstream jobs.
Worked example: caching Node.js dependencies
Add this to .gitlab-ci.yml in your repository root. No special runner permissions are required beyond the standard job permissions; the runner writes the cache to GitLab-managed storage on behalf of the project.
install_dependencies:
stage: build
script:
- npm ci
cache:
key: ${CI_COMMIT_REF_SLUG}-${CI_JOB_NAME}
paths:
- node_modules/
policy: pull-push
run_tests:
stage: test
script:
- npm test
cache:
key: ${CI_COMMIT_REF_SLUG}-${CI_JOB_NAME}
paths:
- node_modules/
policy: pull
Placeholders: replace npm ci and npm test with your package manager commands. CI_COMMIT_REF_SLUG is a GitLab CI variable containing a sanitized branch or tag name, so feature branches do not share caches with main. CI_JOB_NAME separates this job's cache from other jobs in the same pipeline.
Expected behavior on the first run: the job log shows Creating cache after the script. On the second run on the same branch: the log shows Restoring cache before the script, and npm ci should complete faster because node_modules/ already exists.
Risk: if you edit package.json and add a dependency, the cached node_modules/ may be incomplete until npm ci updates it. GitLab caches the directory as uploaded; it does not detect manifest changes automatically. Branch-scoped keys also mean dependencies are not shared between branches.
Limits and how to verify
Caches are stored per project and are subject to size and retention limits set by your GitLab administrator. On GitLab.com SaaS and self-managed instances, defaults and maximums can differ, and caches can be evicted before you expect. Treat the cache as a performance optimization, not a durable store.
Do not cache secrets, credentials, or build outputs that should be reproducible from source. Cache archives are not encrypted by default beyond the underlying storage configuration.
To verify hit behavior, run the pipeline twice on the same feature branch. Compare the job logs: the first run should report creating or uploading the cache, and the second should report restoring it. If both runs create a fresh cache, inspect cache:key for variables that change every commit, such as CI_COMMIT_SHA, which defeats reuse.
To check your instance's limits, open your project's CI/CD settings or ask your administrator about cache expiration and maximum size on the target GitLab instance.
Rolling back
Because this change lives in .gitlab-ci.yml, rollback is a revert of the commit that added or edited the cache block. Merge the revert, and the next pipeline runs without cache restoration or upload.
Actionable closing
Start with one slow dependency-install job. Give it a branch-scoped key and pull-push policy, then compare two pipeline runs. If the second run restores the cache and installs faster, expand the pattern to other jobs. If the cache archive is large enough to dominate job startup time, narrow cache:paths to only the directories that actually change between installs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.