GitLab CI/CD
itDevOps and software delivery
GitLab CI/CD
GitLab CI/CD is the continuous integration and continuous delivery system built into GitLab itself. You describe your build, test, and deploy process as YAML in a single file, .gitlab-ci.yml, committed at the root of your repository. Push a commit, and GitLab reads that file, schedules the work it describes, and reports the result back on the commit and the merge request. There is no separate CI server to install: GitLab hosts the scheduling and the interface, and either GitLab or your own infrastructure supplies the machines that actually run the work.
The mental model
A pipeline is the top-level run: everything triggered for one commit, merge request, schedule, or manual click. A pipeline is composed of stages, which run in order, and jobs, which live inside a stage. Jobs in the same stage run in parallel; if any job in a stage fails, later stages are normally skipped and the pipeline stops early. A small pipeline might look like: a build stage with a compile job, a test stage with test1 and test2 jobs that only start once compile succeeds, and a deploy stage with a deploy-to-production job that only starts once both test jobs succeed.
build-job:
stage: build
script:
- echo "Building..."
test-job1:
stage: test
script:
- echo "Testing part 1"
test-job2:
stage: test
script:
- echo "Testing part 2, this one takes a while"
- sleep 20
deploy-prod:
stage: deploy
script:
- echo "Deploying..."
environment: production
stage places a job in the sequence. script is the list of shell commands the job actually runs. Everything else in GitLab CI/CD — variables, caching, rules, deployments — attaches to this same stage-and-job skeleton.
Runners do the work
A runner is the agent that executes a job: it picks up the job, prepares the environment described in the YAML (often a container image), and runs the script. GitLab.com provides shared, GitLab-hosted runners for Linux, Windows, and macOS with no setup — fully managed, available immediately, a fresh virtual machine for every job. If you need custom hardware, a private network, or full control over the execution environment, you install and register your own runner, choosing when zero-maintenance managed compute isn't the right fit and when you want it back on your own infrastructure instead.
Variables: configuration without hardcoding
CI/CD variables let you pass configuration and secrets into jobs without hardcoding them in the YAML. You can define non-sensitive ones directly in .gitlab-ci.yml:
variables:
ALL_JOBS_VAR: "A default variable"
job1:
script:
- echo "The variable is '$ALL_JOBS_VAR'"
Sensitive values — tokens, passwords, credentials — belong in the project's CI/CD settings instead, where they can be masked (hidden from job logs) and protected (only available to pipelines running on protected branches or tags). Masking is not a hard security guarantee: GitLab itself documents it as not a guaranteed way to prevent a determined malicious user from exfiltrating a value, so anything genuinely sensitive is a candidate for external secrets management, not just a masked project variable. Every merge request that touches .gitlab-ci.yml deserves careful review for exactly this reason — a malicious job can curl the value of a variable to an external server just as easily as a legitimate job can use it to log in to a database.
Runs that fit how you actually ship
GitLab CI/CD pipelines aren't limited to "run everything on every push." Pipelines can run for merge requests only, so you validate a change before it merges rather than after; the needs keyword lets jobs run based on their actual dependencies instead of waiting for an entire stage to finish, which shortens pipeline duration; and larger pipelines can be split into parent-child pipelines within one project (common in monorepos) or trigger pipelines in other projects entirely.
Deploying with environments
An environment represents a deployment target — staging, production, a per-branch review app. Environments track what's currently deployed where, so you can see history and roll back, and they let you scope variables and protections to specific targets rather than applying them globally:
deploy_staging:
stage: deploy
script:
- echo "Deploy to staging server"
environment:
name: staging
url: https://staging.example.com
Dynamic environments extend this per merge request or per branch, commonly for review apps: the environment name is built from a variable like $CI_COMMIT_REF_SLUG, and an on_stop job tears the environment down automatically when the branch is deleted or merged.
Reusing pipeline configuration
Copy-pasting the same jobs across projects doesn't scale. CI/CD components are versioned, reusable pipeline configuration units — a job, a group of jobs, or an entire pipeline — that you pull into your own .gitlab-ci.yml with include: component, pinned to a specific released version. Published components are searchable in the CI/CD Catalog, so a security scan or a deployment pattern another team has already built and tested doesn't need to be rebuilt from scratch. Because included configuration merges into your pipeline, a component and your own jobs sharing a name will collide — worth knowing before you wonder why a job you didn't write showed up, or why one you did write got silently overwritten.
A useful first journey
- Add a
.gitlab-ci.ymlwith two or three simple stages, following the official quickstart, and watch it run in Build > Pipelines. - Split a stage into two jobs and use
needsto let the independent one start immediately instead of waiting for its stage. - Add a project CI/CD variable, mask it, and consume it from a job script instead of hardcoding a value.
- Define a
stagingenvironment for a deploy job and watch it appear under Operate > Environments. - Read the pipeline security model before anything in the pipeline touches production: protected variables, protected runners, and who can review changes to
.gitlab-ci.ymlbefore they merge.
Where GitLab CI/CD is not automatically the answer
GitLab CI/CD assumes your source of truth is a GitLab repository — if your team is split across multiple Git hosts, the tight repository integration buys you less. GitLab-hosted runners are metered by compute minutes: Free-tier namespaces get 400 compute minutes a month, and heavier CI/CD usage either needs a paid tier, purchased additional minutes, or self-managed runners, which shifts the cost from a subscription line item to infrastructure you now operate and secure yourself. Choose GitLab CI/CD because the tight repository integration, the pipeline model, and the runner ecosystem solve a real constraint for you — not merely because it's the CI/CD system that was already sitting there when you created the project.
