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
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 mainThis 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
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: build → test → deploy. 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
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
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 testThe 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
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-jobcache 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
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
rulesorworkflow: rulesfor 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: truefor 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, nonpm→ '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.