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:

FeatureCacheArtifacts
Use CaseExternal dependencies (e.g., node_modules)Build outputs or reports (e.g., test results)
LifetimeShared across jobs and pipelines—expires based on your settingsPassed between jobs in the same pipeline
StorageCan be stored externally (e.g., AWS S3)Stored in GitLab (default)
Policypull, push, pull-pushAlways uploaded on job success or failure (configurable)

Configuring cache:policy

Use the policy keyword to control download/upload behavior:

  • pull – only restore an existing cache
  • push – only upload a new cache
  • pull-push (default) – restore first, then upload after job success

The image shows a GitLab documentation page about the `cache:policy` keyword in CI/CD YAML syntax, explaining how to configure cache upload and download behavior with possible inputs like `pull`, `push`, and `pull-push`.

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:

The image shows a GitLab CI/CD pipeline interface with various pipeline statuses such as "Running," "Skipped," and "Failed." It includes details like pipeline IDs, branches, and user avatars.

Within the project view you’ll see jobs like unit_testing and code_coverage:

The image shows a GitLab CI/CD pipeline interface for a project named "Solar System NodeJS Pipeline," displaying the status of jobs like "code_coverage" and "unit_testing." The sidebar includes options for managing the project, such as issues, merge requests, and pipelines.

First Run: Cache Miss

On the initial run, no cache exists. The pipeline installs dependencies and then uploads the cache:

The image shows a GitLab CI/CD pipeline job interface with a successful unit testing job. The console output details the steps executed, and the job status is marked as "passed."

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:

The image shows a GitLab CI/CD pipeline job interface with a successful unit testing job. The console output displays steps like restoring cache, executing scripts, and uploading artifacts.

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.

Watch Video

Watch video content

Previous
Exploring Reports through Merge Request