Architecture Note: Docker Layer Caching in Bitbucket Pipelines
A minimal architecture for Docker layer caching in Bitbucket Pipelines, covering cache configuration, secret handling, operational checks, and redesign triggers.
16 Jul 2025, 09:29 UTC

Architecture Note: Docker Layer Caching in Bitbucket Pipelines
Requirements
The source code resides in a Bitbucket repository and is the trusted input for every build. Docker images are assembled inside Bitbucket Pipelines, which executes each step in an isolated Docker container. The build must reuse previously downloaded or compiled layers through a cache directory declared in the pipeline configuration. Build secrets—such as registry credentials—are supplied through Bitbucket repository variables so they never appear in source control. The design allows exactly one external dependency: an optional container registry for the final image push.
Smallest Suitable Design
A single bitbucket-pipelines.yml file is sufficient. It checks out the repository, restores a cache directory, builds the image, and optionally pushes it. The cache directory is declared under the caches key and points to a path inside the workspace, for example .docker/cache. The build step consumes that restored directory so Docker can reuse layers instead of fetching or recomputing them.
pipelines:
default:
- step:
name: Build and cache Docker layers
caches:
- paths:
- .docker/cache
script:
- docker build -t my-app:$BITBUCKET_COMMIT .
- docker tag my-app:$BITBUCKET_COMMIT my-registry/my-app:$BITBUCKET_COMMIT
- echo $REGISTRY_PASSWORD | docker login my-registry -u $REGISTRY_USER --password-stdin
- docker push my-registry/my-app:$BITBUCKET_COMMIT
Credentials are injected as secured repository variables. The --password-stdin flag keeps the secret out of the process list and logs. The Dockerfile should order instructions from least to most frequently changing so that dependency installation layers remain valid across commits.
Trust and Data Boundaries
Source code and the Dockerfile are trusted inputs; anything they contain can execute inside the build container. Bitbucket Pipelines provides the runtime isolation, and the cache is stored in Bitbucket's internal storage scoped to the repository, which prevents cross-repo access. The external registry is the only trust boundary that leaves the platform, and it is protected by repository variables that Bitbucket masks in logs.
Operational Checks
- Compare build durations between consecutive runs; a meaningful drop suggests layer reuse.
- Inspect logs for cache-related entries that indicate layers were pulled from the restored directory rather than rebuilt.
- Print the cache directory size with
du -sh .docker/cacheand track it against the repository's cache quota. - Search pipeline logs for variable names or values to confirm secrets are not echoed.
- Verify that the registry received the expected tag after a push.
Failure Modes
- Stale or corrupt cache: a changed base image or invalid layer can cause build failures or silently incorrect artifacts. Detect this through failed steps or unexpected image behavior.
- Cache eviction: if the directory grows past Bitbucket's quota, the cache may be dropped and the next build reverts to a full rebuild.
- Secret leakage: an accidental
echo $SECRETor verbose Docker login can expose credentials. - Registry authentication failure: expired or mis-scoped variables abort the push step.
- Resource exhaustion: large images or parallel steps can exceed the memory or time limits of the Pipelines runner.
Conditions That Would Change the Design
- Cache quota exhaustion: many concurrent pipelines or very large layer sets can exceed the undocumented Bitbucket cache limits, at which point an external cache backend (for example, a registry with cache manifests or a remote BuildKit cache) becomes necessary.
- Platform migration: moving to Jenkins, GitHub Actions, or another CI system requires a new cache strategy because cache semantics differ.
- Multi-architecture or BuildKit adoption: building for multiple architectures or using BuildKit cache mounts changes how layers are stored and shared, which may require updating the Dockerfile and pipeline syntax.
- Regulatory constraints: mandates for on-premises CI invalidate the cloud-only Pipelines model and push the build into self-managed infrastructure.
Verification Steps
- Create a test repository with a minimal Dockerfile and the pipeline configuration above.
- Trigger two consecutive builds on the same branch. Confirm the second build is faster and that its logs show layer reuse rather than a cold download.
- Add a temporary script line such as
du -sh .docker/cacheto observe the cache footprint, then remove it. - Use the Bitbucket REST API to fetch the pipeline logs and grep for the names of your secret variables to ensure they are absent.
- Intentionally set an invalid registry password in a test branch and confirm the pipeline fails at the login step before any push is attempted.
This architecture keeps the moving parts inside Bitbucket, uses repository variables for secrets, and treats the cache as a performance optimization rather than a source of truth. It is appropriate for small to medium projects until cache size, concurrency, or compliance requirements force a more distributed design.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.