GitLab CI/CD: Architecting, Deploying, and Optimizing Pipelines
Continuous Integration with GitLab
Caching Dependencies
When your project grows, installing dozens or hundreds of npm packages on every CI run can easily add minutes to your pipeline. By caching the node_modules
directory in GitLab CI, you can reduce install time from ~7 s to ~1 s per job and save runner resources.
Example package.json for “Solar System” App
Here’s a simplified package.json
for our Node.js service:
{
"name": "Solar System",
"version": "6.7.6",
"author": "Siddharth Barahalikar <[email protected]>",
"license": "MIT",
"scripts": {
"start": "node app.js",
"test": "mocha app-test.js --timeout 10000 --reporter mocha-junit-reporter --exit",
"coverage": "nyc --reporter cobertura --reporter lcov --reporter text --reporter json-summary mocha"
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^5.13.20",
"nodemon": "^3.0.2",
"nyc": "^15.1.0"
},
"devDependencies": {
"chai": "*",
"chai-http": "*",
"mocha": "*"
}
}
Running npm install
generates package-lock.json
and populates node_modules
. In GitLab CI, each job running npm install
repeats this process:
$ npm install
added 364 packages in 7s
2 vulnerabilities (1 high, 1 critical)
$ npm test
> Solar [email protected] test
> mocha app-test.js …
Cache vs. Artifacts
GitLab CI offers both cache and artifacts, but they serve different purposes:
Feature | Cache | Artifacts |
---|---|---|
Use Case | External dependencies (e.g., node_modules ) | Build outputs or reports (e.g., test results) |
Lifetime | Shared across jobs and pipelines—expires based on your settings | Passed between jobs in the same pipeline |
Storage | Can be stored externally (e.g., AWS S3) | Stored in GitLab (default) |
Policy | pull , push , pull-push | Always uploaded on job success or failure (configurable) |
Configuring cache:policy
Use the policy
keyword to control download/upload behavior:
pull
– only restore an existing cachepush
– only upload a new cachepull-push
(default) – restore first, then upload after job success
Adding Cache to .gitlab-ci.yml
Below is a minimal configuration that caches node_modules
for unit_testing
and code_coverage
jobs:
stages:
- test
.default_cache: &default_cache
key:
files:
- package-lock.json
prefix: node_modules
paths:
- node_modules
policy: pull-push
when: on_success
unit_testing:
stage: test
image: node:17-alpine3.14
cache: *default_cache
before_script:
- npm install
script:
- npm test
artifacts:
when: always
expire_in: 3 days
name: Mocha-Test-Result
paths:
- test-results.xml
reports:
junit: test-results.xml
code_coverage:
stage: test
image: node:17-alpine3.14
cache: *default_cache
before_script:
- npm install
script:
- npm run coverage
Tip
Using package-lock.json
in the cache key ensures the cache is invalidated automatically whenever your dependencies change.
Viewing the Pipeline
After committing .gitlab-ci.yml
, GitLab triggers a pipeline. The Pipelines page shows status and stages:
Within the project view you’ll see jobs like unit_testing
and code_coverage
:
First Run: Cache Miss
On the initial run, no cache exists. The pipeline installs dependencies and then uploads the cache:
Restoring cache
No cache found for key: node_modules-<sha256-of-package-lock.json>
$ npm install
added 364 packages in 7s
$ npm test …
Saving cache for successful job
Created cache node_modules-<sha>-non_protected
Uploading cache.zip to GitLab Runner storage...
Subsequent Run: Cache Hit
With no changes to package-lock.json
, the cache restores instantly and npm install
completes in ~1 s:
Restoring cache
Found cache: node_modules-<sha256> ...
$ npm install
up to date, audited 365 packages in 1s
$ npm test …
Clearing or Invalidating Cache
You can manually clear caches via Settings → CI/CD → Clear runner caches.
To automate invalidation, use package-lock.json
in your cache:key
as shown above.
Warning
Clearing caches too frequently may negate performance gains. Only clear when dependencies are truly out of sync.
Links and References
Watch Video
Watch video content