GitLab CI/CD
Repository, pipelines, registry, and releases in one system you can run inside your own network.
GitLab’s pitch is that everything lives in one place: source, merge requests, pipelines, container registry, packages, issues, releases. On the public SaaS that’s a convenience. On an on-premise install inside a closed network, it’s often the only way to have GitHub-shaped workflows at all — which is why so many enterprise and public-sector teams land here.
The CI is not a bolt-on either. It’s the same YAML-in-the-repo model as GitHub Actions, with a runner architecture designed to be self-hosted from the start.
A first pipeline
.gitlab-ci.yml at the repository root:
stages: [test, build, deploy]
default:
image: node:22-alpine
cache:
key:
files: [package-lock.json]
paths: [.npm/]
test:
stage: test
script:
- npm ci --cache .npm --prefer-offline
- npm run lint
- npm test
artifacts:
reports:
junit: reports/junit.xml
build:
stage: build
script: npm run build
artifacts:
paths: [dist/]
expire_in: 1 week
deploy:
stage: deploy
script: ./deploy.sh
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Push and it runs. Jobs in the same stage run in parallel; stages run in order — or skip
the ordering entirely with needs: to build a DAG where each job starts the moment its
own inputs are ready.
Things worth knowing early
| Feature | Why it matters |
|---|---|
rules: | Decides whether a job runs at all. Replaces the older only/except |
artifacts: | Files passed to later stages, and shown in the merge request |
environment: | Tracks what is deployed where, with a one-click rollback |
include: | Pull shared pipeline templates from another repository |
parallel: matrix: | The version and platform matrix, same idea as elsewhere |
| Review apps | An ephemeral deployment per merge request |
Variables live in Settings → CI/CD → Variables. Mark secrets masked so they’re hidden in logs, and protected so they’re only exposed on protected branches — the second flag is the one people forget, and it’s the one that keeps a fork’s pipeline away from production credentials.
Runners are the real decision
A runner is the machine that executes jobs, and where it lives shapes both the bill and the security story.
- GitLab.com shared runners — 400 compute minutes a month on the free tier. Fine for a small project, exhausted quickly by a real one.
- Your own runners — free and unlimited on every tier, including free. Install
gitlab-runner, register it against the project or group, pick an executor (Docker is the usual choice), and the minute pool stops mattering. - Self-managed GitLab — the Community Edition is free software; you pay in infrastructure and in operations.
gitlab-runner register \
--url https://gitlab.example.com/ \
--token <runner-token> \
--executor docker \
--docker-image alpine:latest
Tag runners (gpu, windows, prod-network) and select them per job with tags:.
Untagged jobs land on whatever is free, which is how a build ends up on the one machine
with production access.
Before you choose self-managed
Running GitLab yourself means you own upgrades, backups, and a runner fleet — a real, recurring job, not a weekend install. Take it on when the requirement is genuine: source that cannot leave the network, an auditor who wants one system of record, or an environment with no internet route at all. If none of those apply, GitLab.com or GitHub will cost you less attention.
Next
Before it ships, a gate on the code itself → SonarQube
Last updated 23 Aug 2026, 00:00 UTC.