Improving CI Performance (aka How To Save Your Money)
By: Golosay Volodymyr
Tags: DevOps, Programming, Software Development, Ci Cd Pipeline, Gitlab

Improving CI Performance (aka How To Save Your Money)

How to improve CI/CD pipeline speed with using caching and Docker images

Reducing the pipeline duration will save your money

Having CI on a project nowadays is a must-have thing. In most cases, we have to pay for the time needed for dependencies installation, project build, tests, deployment. If you feel it’s your case, I will share my experience of how to improve significantly the performance of Angular app (and not only) CI and reduce billing costs in times.

Pipeline improvements

Dependencies installation

Almost every pipeline starts from dependencies installation. To deal with it, usually, we use the simple command npm install or yarn. In our situation, when we run it as part of CI, we check package-lock. If it has the same versions as package.json it will install everything from the lock file. But if package.json has newer versions, it will regenerate the package-lock file.

npm ci instead of npm install

This command: npm ci is similar to npm install , except it’s preferable to use it in automated environments such as Continuous Integration. It can be significantly faster than a regular npm install by skipping certain user-oriented features. Also, it has a super cool feature, it breaks with an error if package.json is updated without package-lock. So you can be sure, you always will have the same versions of dependencies between builds. Ordinary npm install in such case can install a newer version and generate the new package-lock.json.

npm ci with cache

Npm cache is located in ~/.npm , but in most CIs you can only cache things inside your working directory.

What we can do to avoid this, is changing the cache directory to a local folder in your project with npm set cache .npm. The NPM cache now is in ./.npm , so now it’s cached between CI jobs.

For example, we use the Gitlab CI, where we need to set-up caching in .gitlab-ci.yml.

cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/ - .npm/

After that, we can install dependencies with the next command:

npm ci — cache .npm — prefer-offline

Now our CI uses cache and shares it with our next pipeline runs.

Angular tests speed can be faster up to 3 times (until Angular bug exist, the issue is valid on 14 Dec 2020)

A few weeks ago, I found an article about an issue with Angular tests. The issue, in general, is about broken shared styles clean up after each test. The framework adds a style tag with shared styles after each test. At the end of your tests, you can have thousands of style tags with shared styles on the test page.

The author provided a temporary workaround, how to solve this issue until an official fix.

You should simply add a few lines of code to your project:

In a CLI project, open test.ts and add the following lines:

import { ɵDomSharedStylesHost } from ‘@angular/platform-browser’; afterEach(() => { getTestBed().inject(ɵDomSharedStylesHost).ngOnDestroy(); });

Amazingly, but it works! Share in comments did it help you?

Docker improvements

According to the 2020 Jetbrains developer survey, 44% of developers are now using some form of CI and CD with Docker containers. So that I would like to share some common best practices. Each of them will improve performance for any NodeJS app, and it doesn’t matter, do you use Angular, React, Vue, or vanilla js.

Use smaller Docker base image

Four building an Angular app or simple SSR it’s enough to use smaller Docker images, such as Slim and Alpine Linux modifications. It will reduce the duration of the building, pushing and pulling images on your CI.

# base node image

FROM node:12-slim

Use cache for rebuilding your images and rerunning pipelines

With a good configured caching strategy, you can rebuild your image almost immediately. Rules are pretty simple:

install modules before copying source files do not use labels LABEL build_number=”123"

# base node image FROM node:12.20.0-slim WORKDIR /usr/src/app COPY package*.json ./ RUN npm ci

Use an explicit image version instead of the latest.

When you use the latest/stable reference as the base image, you can not be sure which version will be the latest/stable exactly. So it’s much better to use an exact version of the base image.

FROM node:12.20.0-alpine3.10 as builder

Host your own CI/CD runner

Own runner, first of all, can bring better network speed. GCP/AWS/Digital Ocean has much better network bandwidth than built-in runners, so you will save time on installing dependencies.

Secondary, if you need, you can use machines with better CPU/RAM, it will be faster, but not sure it can save your money 😉.

Do not install all dependencies on every run

You can save a lot of time if you use Docker with preinstalled dependencies. At least you can preinstall in Docker image global dependencies such as CLI. But if you work with a project, where dependencies receive updates rarely. It might be a good choice, to preinstall all dependencies and copy your sources to the correct folder. After that, you can run any command without dependencies installation.

That's it. With these simple recommendations, you can significantly save time and money.

Thanks for reading! If you are interested in building SEO-friendly Angular app, it might be useful to read my previous article.