Skip to content
CloudOps
Newsletter
All guides
AI for GitLab CI/CD By James Joyner IV · · 9 min read

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:

  1. Is it a hit or a miss? The job log says Successfully extracted cache (hit) or No URL provided, cache will not be downloaded (misconfig). Read it.
  2. 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.
  3. Is the path right? Cache paths must be inside $CI_PROJECT_DIR. A node_modules outside the project dir won’t be cached.
  4. 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.

Free download · 368-page PDF

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.