CI Performance: Multi-stage caching
2023-08-31
Learn how to apply Docker multi-stage builds and its built-in caching to make most CI processes significantly more performant for a variety of scenarios.
The article discusses optimizing CI performance using Docker multi-stage builds for a React project. Key goals include:
- Rerunning builds only when dependencies change
- Skipping dependency resolution for source code changes
- Avoiding unnecessary builds for non-code changes
Initial Project Setup
npm install
npm run build
npm run test -- --watchAll=false
docker build . --tag npm-demo
Initial Dockerfile
FROM nginx:1.25.2
COPY build /usr/share/nginx/html
ENTRYPOINT ["nginx", "-g", "daemon off;"]
Multi-stage Migration Dockerfile
FROM node:20-alpine3.17 as ci_step_build
WORKDIR /app
COPY . /app/
RUN npm install
RUN npm run build
RUN npm run test -- --watchAll=false
FROM nginx:1.25.2
COPY --from=ci_step_build /app/build /usr/share/nginx/html
ENTRYPOINT ["nginx", "-g", "daemon off;"]
Optimized Dockerfile
FROM node:20-alpine3.17 as ci_step_build
WORKDIR /app
COPY package.json /app/package.json
COPY package-lock.json /app/package-lock.json
RUN npm install
COPY src/ /app/src/
COPY public/ /app/public/
RUN npm run build
RUN npm run test -- --watchAll=false
FROM nginx:1.25.2
COPY --from=ci_step_build /app/build /usr/share/nginx/html
ENTRYPOINT ["nginx", "-g", "daemon off;"]
Performance Results
- Package.json changes: ~20s
- Source file changes: ~9s
- README changes: ~1s
- No changes: ~1s

The optimized approach significantly reduces build times by leveraging Docker's layer caching. By copying dependency files first and running npm install before copying source code, we ensure that dependency installation is only re-run when package files change, not when source code changes.