Skip to content
Learni
View all tutorials
DevOps

How to Set Up GitLab CI/CD in 2026

12 minBEGINNER
Lire en français

Introduction

GitLab CI/CD is a powerful tool integrated into GitLab for automating development pipelines: builds, tests, and deployments. In 2026, with the rise of continuous deployments, mastering GitLab CI/CD is essential for every developer, even beginners. Unlike Jenkins, which requires server setup, GitLab CI/CD is free on GitLab.com, fully managed, and scalable.

This tutorial guides you step by step to create a complete pipeline for a simple Node.js project. You'll learn the basics of the .gitlab-ci.yml file, stages, parallel jobs, Docker, artifacts, and variables. By the end, you'll have a professional workflow endorsed by senior devs. Imagine: a push to main triggers tests, builds, and auto-previews—no manual work. Ready to boost your productivity? (128 words)

Prerequisites

  • Free account on GitLab.com
  • Git installed (version 2.30+)
  • Node.js 20+ for the example
  • Basic knowledge of YAML and Bash (strict indentation!)
  • A new empty project on GitLab (create via 'New Project > Create blank project')

Initialize the Local Node.js Project

terminal
mkdir mon-app-ci
cd mon-app-ci
npm init -y
npm install express
mkdir src
cat > src/index.js << 'EOF'
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello GitLab CI/CD!'));
app.listen(3000, () => console.log('Server on 3000'));
EOF
cat > package.json << 'EOF'
{
  "name": "mon-app-ci",
  "version": "1.0.0",
  "scripts": {
    "start": "node src/index.js",
    "test": "echo 'Tests OK' && exit 0"
  }
}
EOF
git init
git add .
git commit -m 'Initial commit'
git remote add origin https://gitlab.com/YOUR_USERNAME/mon-app-ci.git
git branch -M main
git push -u origin main

This script sets up a minimal Node.js project with an Express server and a mock test. It commits and pushes to your GitLab repo (replace YOUR_USERNAME). Once pushed, GitLab will automatically detect CI/CD later when .gitlab-ci.yml exists. Pitfall: Double-check the exact remote URL to avoid 403 errors.

First Pipeline: Basic Job

Now, add your first .gitlab-ci.yml file to the root. GitLab runs jobs on shared runners (free). Every push triggers the pipeline, visible in 'CI/CD > Pipelines'. Let's start simple: a job that prints a message.

First Simple .gitlab-ci.yml

.gitlab-ci.yml
hello-job:
  stage: build
  script:
    - echo "Hello GitLab CI/CD !"
    - echo "Pipeline déclenché avec succès."

This YAML defines a hello-job in the build stage (implicitly defined). The script runs line by line on a Linux runner. Copy-paste, commit, and push: watch the green pipeline in 10 seconds. Pitfall: YAML is indentation-sensitive (2 spaces, no tabs).

Structuring with Stages

Stages organize jobs in sequence: buildtestdeploy. Jobs in one stage finish before the next. Let's add a test stage with npm test. Parallel jobs in the same stage speed things up.

.gitlab-ci.yml with Build/Test Stages

.gitlab-ci.yml
stages:
  - build
  - test

build-job:
  stage: build
  script:
    - echo "Installation des dépendances"
    - npm install
    - echo "Build OK"

test-job:
  stage: test
  script:
    - echo "Lancement des tests"
    - npm test
  needs: [build-job]

Here, stages sets the order. test-job depends on build-job via needs for optional parallelism. npm install simulates a real build. The pipeline moves to test only if build succeeds. Pitfall: Without needs, jobs run in parallel; skip npm ci in prod for speed.

Using Docker for Isolation

GitLab runners use Docker images by default. Specify image: node:20 for a reproducible Node environment. It's like a disposable container: no pollution between jobs. Let's add a lint job.

.gitlab-ci.yml with Docker and Lint

.gitlab-ci.yml
image: node:20

stages:
  - build
  - test
  - lint

build-job:
  stage: build
  script:
    - npm install

lint-job:
  stage: lint
  script:
    - npm install -g eslint
    - eslint src/index.js
  needs: [build-job]

test-job:
  stage: test
  script:
    - npm test

The global image applies Node 20 to all jobs. lint-job installs ESLint dynamically (in prod, add to package.json). Jobs in different stages wait. Pitfall: Mounted volumes persist; use before_script for common setup.

.gitlab-ci.yml with Artifacts and Cache

.gitlab-ci.yml
image: node:20

stages:
  - build
  - test

cache:
  paths:
    - node_modules/

variables:
  NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.npm"

build-job:
  stage: build
  script:
    - npm install
  artifacts:
    paths:
      - node_modules/
    expire_in: 1 hour

test-job:
  stage: test
  script:
    - npm test
  dependencies:
    - build-job

cache speeds up npm install (reuses node_modules). artifacts passes files to the next job (downloadable in UI). dependencies links artifacts. In prod, this cuts times by 5x. Pitfall: Cache per branch; expire artifacts to save storage.

Automated Deployments

Add a conditional deploy stage on main. Use GitLab variables (Settings > CI/CD > Variables) for secrets like HEROKU_API_KEY. Rule: Never commit secrets!

Complete .gitlab-ci.yml with Deploy

.gitlab-ci.yml
image: node:20

stages:
  - build
  - test
  - deploy

cache: {}

build-job:
  stage: build
  script: [npm install]
  artifacts:
    paths: [dist/, node_modules/]
    expire_in: 30 mins

test-job:
  stage: test
  script: [npm test]
  dependencies: [build-job]

deploy-job:
  stage: deploy
  script:
    - apt-get update -qq && apt-get install -y -qq heroku
    - heroku login -i --token $HEROKU_TOKEN
    - git push heroku main
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment: production
  dependencies: [build-job]

deploy stage runs only on main via rules. HEROKU_TOKEN is a masked GitLab variable. environment tracks deployments. Adapt for Vercel/Netlify. Pitfall: Test rules in merge requests; apt-get for runtime tools.

Best Practices

  • Use rules or workflow: rules for conditional pipelines (branches, tags).
  • Cache node_modules and .npm: Save 80% on install time.
  • Artifacts for reports/tests: JUnit XML for GitLab UI.
  • Masked variables for API keys; protected: true for protected branches.
  • Custom runners for GPU/ML needs: GitLab.com is enough for beginners.

Common Errors to Avoid

  • YAML indentation: Always 2 spaces, no tabs—causes 'jobs:build invalid'.
  • Missing image: Jobs on basic Alpine, no npm → 'command not found'.
  • Secrets in plain text: Never in repo; use GitLab Variables.
  • No needs/dependencies: Jobs redownload everything, slow and wasteful.

Next Steps

Dive into auto-scaling GitLab Runners, Helm charts for Kubernetes, or SonarQube integrations. Check the official GitLab CI docs. For expert training, explore our DevOps courses at Learni. Next challenge: Upgrade to GitLab Premium for manual approvals.