Cutting Bitbucket Pipelines Build Times with Dependency Caches
Bitbucket Pipelines' built-in dependency caches can cut minutes off CI builds with a two-line change. Here's how they behave, a working Maven example, and the trade-offs to know first.
03 Jun 2026, 00:38 UTC

Your CI bill is mostly re-downloading the internet
Watch a fresh Bitbucket Pipelines build sometime and time where the minutes go. For a typical Maven or npm project, the actual compile-and-test work is often a fraction of the step; the rest is dependency resolution — pulling the same jars or packages from remote repositories on every single run, because each build starts in a clean container.
The takeaway: Bitbucket Pipelines has built-in dependency caches that persist those download directories between builds. Enabling one is usually a two-line change, and it commonly turns a multi-minute dependency phase into seconds on repeat runs. This post covers how the caches behave, a working configuration, and the trade-offs you should understand before relying on them.
What a Pipelines cache actually is
A cache in Pipelines is a tarball of a directory that gets saved after a step finishes successfully and restored at the start of later steps. It is a best-effort performance optimization, not a storage guarantee — a build must still pass if the cache is empty or missing.
Bitbucket ships predefined caches for common tools, including maven, gradle, npm, yarn, node, pip, composer, and dotnetcore. Each maps to the conventional dependency directory for that tool (for Maven, ~/.m2/repository). You can also define custom caches that point at any path, which is handy for downloaded tooling.
Three behaviors matter in practice:
- Caches are keyed per branch. The first build on a new branch typically falls back to the default branch's cache, then maintains its own copy afterward.
- Caches expire. Unused caches are invalidated after roughly a week, and each cache has a size limit (historically around 1 GB). They suit dependencies, not build outputs.
- Caches are saved only on success. A failed step does not update the cache, which protects you from persisting half-downloaded state in most cases.
Exact size limits, retention windows, and the list of predefined names have changed over time, so confirm the current values in Atlassian's documentation before designing around them.
A worked example: Maven in two lines
Here is a minimal bitbucket-pipelines.yml for a Maven project, committed to the repo root. No special permissions are needed beyond the ability to push to the repository:
image: maven:3.9-eclipse-temurin-17
pipelines:
default:
- step:
name: Build and test
caches:
- maven
script:
- mvn -B verifyThe caches: - maven entry is the entire change. On the first run on a branch, the cache is empty and Maven downloads everything — expect the usual slow build. On subsequent runs, the step log will show the cache being downloaded at setup and re-uploaded at teardown, and ~/.m2/repository is already warm.
To verify it is working, open the step log and look for the cache teardown section, then check the Caches tab in the Pipelines UI — it shows each cache's size and last-used timestamp. Compare the step duration of the first run against a rerun on the same branch; the dependency phase is where you will see the difference.
For a custom tool, declare the path explicitly:
definitions:
caches:
my-tool: .tools/my-tool
pipelines:
default:
- step:
caches:
- my-tool
script:
- ./scripts/ensure-tool.shCaches are not artifacts
The most common design mistake is using a cache to pass data between steps in the same build. Don't. Caches are shared across builds and branches, restored on a best-effort basis, and can be absent on any given run. If step two needs the compiled output of step one, use artifacts — they are guaranteed to be available to later steps in the same pipeline, which is a correctness mechanism rather than a performance hint.
The rule of thumb: if losing the data would break the build, it is an artifact. If losing the data would only make the build slower, it is a cache.
Trade-offs and failure modes
Stale or corrupted caches. A poisoned cache can cause confusing failures — for example, a partially written jar that Maven refuses to re-download. Your pipeline must survive a cold cache. Test this once: clear the cache from the Pipelines UI (Caches tab) and confirm the build still passes. If it doesn't, you have a hidden dependency on cached state that will bite you after an expiry.
Size limits. An ~/.m2/repository that accumulates every snapshot your project has ever built can push against the per-cache limit, and oversized caches slow down the download/upload phase they were meant to speed up. Prune aggressively, or accept periodic clears.
Branch proliferation. Because caches are per branch, a burst of short-lived feature branches each creates its own copy. They expire on their own, but the first build on each branch pays the cold-cache cost.
Never cache secrets. Cache contents persist across builds and are accessible to anything running in the pipeline. Keep credentials out of cached paths — use secured repository variables instead.
Where to start
Pick your slowest pipeline, add the matching predefined cache, and rerun the build twice on the same branch. Check the Caches tab and the step log to confirm the round trip, then do one manual clear to prove the build is cache-independent. It is one of the cheapest CI wins available — two lines of YAML in exchange for getting those minutes back on every commit.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.