As we all know, caching is an important technique for performance optimization. In CI/CD, proper use of cache can significantly improve pipeline build speed!
Below we use a frontend NodeJS project as an example to demonstrate two effective caching methods.
First, let's prepare a package.json with these modules:
{
"dependencies": {
"angular": "^1.8.3",
"eslint": "^9.15.0",
"jest": "^29.7.0",
"koa": "^2.15.3",
"next": "^15.0.3",
"nuxt": "^3.14.159",
"react": "^18.3.1",
"vue": "^3.5.13",
"webpack": "^5.96.1"
}
}
Run npm install in pipeline with this configuration:
main:
push:
"no-cache":
docker:
image: node:22-alpine
stages:
- name: npm install
script: npm install
Execution result:

Without cache, it downloads resources from network, taking about 23s.
Cloud Native Build leverages Docker's volumes feature. You can declare pipeline.docker.volumes to mount host directories into containers. Build tasks can store dependencies in host cache for future pipelines.
Node pipeline configuration:
main:
push:
"volume-cache":
docker:
image: node:22-alpine
volumes:
- node_modules:copy-on-write
stages:
- name: npm install
script: npm install
After several executions with cache hit:

The message "added 1973 packages from 1072 contributors" disappears, replaced by "up to date". No network download needed, time reduced to 13s.
The drawback of volumes is that cache only works on current build machine. Cloud Native Build dynamically allocates build machines based on project concurrency. If subsequent pipelines get assigned to different machines without cache, it will download from network again.
Maven pipeline configuration:
main:
push:
- docker:
# Find your required maven and jdk version at https://hub.docker.com/_/maven
image: maven:3.8.6-openjdk-8
volumes:
- /root/.m2:cow
stages:
- name: build
script: mvn clean package
Gradle pipeline configuration:
master:
push:
- docker:
# Find your required gradle and jdk version at https://hub.docker.com/_/gradle
image: gradle:6.8-jdk8
volumes:
- /root/.gradle:copy-on-write
stages:
- name: build
script: ./gradlew bootJar
Cloud Native Build provides another cache method: run npm install in a container, cache the image locally, and push to remote registry.
For subsequent pipelines, if the build machine has cached image, it will be used directly. Otherwise, it will be pulled from remote registry.
Example of built-in docker:cache task:
master:
push:
- stages:
- name: build cache image
type: docker:cache
options:
dockerfile: cache.dockerfile
by:
- package.json
- package-lock.json
versionBy:
- package-lock.json
exports:
name: DOCKER_CACHE_IMAGE_NAME
- name: use cache
image: $DOCKER_CACHE_IMAGE_NAME
commands:
- cp -r "$NODE_PATH" ./node_modules
Example cache.dockerfile:
# Choose a base image FROM node:16 # Set working directory WORKDIR /space # Copy files listed in 'by' COPY . . # Install dependencies RUN npm ci # Set required environment variables ENV NODE_PATH=/space/node_modules
First execution without cached image (needs to build and push):

Takes about 31.5s, similar to direct npm install.
Effect when pulling cached image on new build machine - to be added
Subsequent execution with locally cached image:

Time reduced to 2.7s - significant improvement!
volumes: Cache on build machine, good effectdocker:cache: Cache on build machine and remote registry, good effectvolumes: Simple configuration, easy to understanddocker:cache: Complex configuration involving multiple files, higher learning curvevolumes: Shared within same build machine, not across machinesdocker:cache: Exclusive during pipeline execution. After pushing to remote, can be shared across pipelines and machinesvolumes: Flexible read/write control, suits more scenariosdocker:cache: Need to rebuild and push new image, other machines need to pull againSee .cnb.yml
For actual comparison, check pipeline results in this repository's Cloud Native Build page, or fork to try yourself.