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
CI Performance Build Results Chart
Performance results showing the dramatic improvement in build times with multi-stage caching.

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.