Artifacts vs. Caching — A Critical Distinction
Both artifacts and caches involve saving files, but they serve opposite purposes and are frequently confused.
| Artifact | Cache | |
|---|---|---|
| Purpose | Pass essential output forward within a run | Speed up future runs |
| Scope | Within a single pipeline run | Across multiple pipeline runs |
| If lost | Pipeline fails | Pipeline runs slower |
| Contents | Build outputs, test reports, binaries | Dependencies, compiled libraries |
The Dependency Download Problem
Every pipeline run typically needs to download project dependencies: node_modules for JavaScript, .m2/repository for Maven, vendor/ for Go, or pip packages for Python. On a cold run, this can take 2–5 minutes and consume hundreds of megabytes of bandwidth. Multiply that by 50 pipeline runs per day across a team, and you're wasting hours of developer time and significant network costs.
A cache stores these downloaded directories between runs:
- Run 1 (cache miss): The pipeline checks for a cached copy of
node_modules. None exists. It runsnpm install, downloading all dependencies. At the end, it savesnode_modulesto the cache, keyed by a hash ofpackage-lock.json. - Run 2 (cache hit): The pipeline checks for a cache. The
package-lock.jsonhash matches. It restoresnode_modulesfrom cache in seconds.npm installeither skips entirely or runs in milliseconds. - Run 3 (cache invalidation): A developer updates
package-lock.json. The hash no longer matches. The pipeline treats this as a cache miss, downloads fresh dependencies, and saves a new cache entry.
The cache key — typically a hash of your dependency manifest file — is what makes this safe. If dependencies change, the cache is automatically invalidated.
Docker Layer Caching
When building Docker images, each instruction in a Dockerfile (RUN, COPY, ADD) creates an immutable layer. Docker can reuse a cached layer if the instruction and all preceding layers are unchanged.
The practical implication: structure your Dockerfile so that slow, rarely-changing steps (like RUN npm install) come before fast, frequently-changing steps (like COPY . .). If you copy your source code first and then install dependencies, every code change invalidates the dependency layer and forces a full reinstall.
# Good: dependencies installed before source code is copied
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./ # Only changes when deps change
RUN npm ci # Cached unless package*.json changes
COPY . . # Changes on every commit — but layer above is cached
RUN npm run build
In CI/CD environments, you can pass --cache-from to docker build to pull a previously built image and use its layers as a cache source, even across different runner machines.
A common interview question is: "Your Docker builds are taking 8 minutes in CI. How would you speed them up?" The expected answer covers two things: (1) restructure the Dockerfile to put slow, stable layers (dependency installs) before volatile layers (source code copies), and (2) use --cache-from with a registry-stored image to share layer cache across ephemeral runners. Mentioning both layer ordering and cross-runner cache sharing signals senior-level understanding.