GitLab CI/CD Basics
CI/CD stands for Continuous Integration and Continuous Delivery. GitLab CI/CD automatically builds, tests, and deploys your code every time someone pushes a change. It replaces manual, error-prone steps with a reliable automated process.
The Problem CI/CD Solves
WITHOUT CI/CD: Dev pushes code → manually runs tests (or forgets to) → manually builds the app → manually uploads to server → hopes nothing broke in production 😬 WITH CI/CD: Dev pushes code → GitLab automatically runs tests → builds the app → deploys to server → reports success or failure with details ✅
Continuous Integration vs Continuous Delivery
| Term | What It Means | Example |
|---|---|---|
| Continuous Integration (CI) | Every push triggers automated tests immediately | Run unit tests on every commit |
| Continuous Delivery (CD) | Successfully tested code is prepared for deployment automatically | Build a Docker image and upload to registry |
| Continuous Deployment | Code goes live automatically after all tests pass | Deploy to production server without human approval |
The .gitlab-ci.yml File
GitLab CI/CD is configured entirely through a file named .gitlab-ci.yml in the root of your repository. This file tells GitLab what to run, when to run it, and in what order.
Repository root: ├── src/ ├── tests/ ├── README.md └── .gitlab-ci.yml ← CI/CD instructions live here
Every time you push code, GitLab reads this file and executes the instructions automatically.
Your First .gitlab-ci.yml File
Here is the simplest possible CI/CD file — it runs one test job:
run-tests:
script:
- echo "Running tests..."
- npm test
Breaking it down:
- run-tests — the name you give this job (can be anything)
- script — the list of shell commands to execute
Stages — Controlling the Order
Stages define the order in which jobs run. All jobs in the same stage run in parallel. The next stage starts only after all jobs in the current stage pass.
stages:
- build
- test
- deploy
build-app:
stage: build
script:
- npm run build
run-unit-tests:
stage: test
script:
- npm test
deploy-to-staging:
stage: deploy
script:
- ./deploy.sh staging
Visual Pipeline Flow
Push code
↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ build │ → │ test │ → │ deploy │
│ build-app│ │unit-tests│ │ staging │
└──────────┘ └──────────┘ └──────────┘
2 min ✅ 4 min ✅ 1 min ✅
↓
App is live on staging
Images — The Environment Your Jobs Run In
Each job runs inside a Docker container. The image keyword specifies which container image to use.
run-tests:
image: node:20 ← use a container with Node.js 20
script:
- npm install
- npm test
build-java:
image: maven:3.9 ← use a container with Java and Maven
script:
- mvn package
This means every job gets a clean, consistent environment regardless of what software is installed on the server running it.
Variables — Storing Configuration Values
Variables store values your scripts need without hardcoding them into the YAML file.
variables:
NODE_ENV: "production"
API_URL: "https://api.example.com"
deploy:
script:
- echo "Deploying to $API_URL"
Sensitive values like passwords and tokens go into CI/CD variables in GitLab settings (not in the YAML file), where they are stored encrypted and masked in logs.
Viewing a Pipeline Run
After pushing code, go to Build → Pipelines. You see a list of recent pipeline runs. Click any run to see the stages and jobs inside it.
Pipeline #112 ● running main Riya 2 min ago Pipeline #111 ✅ passed main Arjun 1 hour ago Pipeline #110 ❌ failed fix/bug Sara 3 hours ago
Click a failed pipeline to see which job failed and read its log output to find the error.
Reading a Job Log
Click any job inside a pipeline to see its full terminal output. The log shows every command that ran and what it printed.
Job: run-unit-tests
─────────────────────────────────────────
$ npm install
added 240 packages in 12s
$ npm test
PASS tests/auth.test.js
PASS tests/search.test.js
FAIL tests/payment.test.js
● Payment › should validate card number
Expected: true
Received: false
Tests: 1 failed, 2 passed
─────────────────────────────────────────
Job failed ❌
Only and Except — Controlling When Jobs Run
Use only to run a job only on specific branches, or except to skip it on certain branches.
deploy-production:
stage: deploy
script:
- ./deploy.sh production
only:
- main ← this job runs ONLY when code is pushed to main
run-tests:
stage: test
script:
- npm test
except:
- docs/* ← skip tests for documentation-only branches
Rules — The Modern Alternative to Only/Except
The rules keyword gives more precise control. It evaluates conditions in order and applies the first match.
deploy:
script:
- ./deploy.sh
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: on_success
- when: never ← do not run on any other branch
Artifacts — Saving Job Output
An artifact is a file or folder that a job produces and saves so other jobs (or you) can download it later.
build-app:
stage: build
script:
- npm run build ← produces the dist/ folder
artifacts:
paths:
- dist/ ← save dist/ for later jobs
expire_in: 1 week ← delete after 1 week
deploy:
stage: deploy
script:
- cp -r dist/ /var/www/html ← use the saved dist/ from build
Artifacts also appear as downloadable files on the pipeline page so developers can inspect build outputs directly.
