Table of Contents
Understanding GitLab CI/CD in a Linux Context
GitLab CI/CD is tightly integrated into GitLab repositories and is commonly run on Linux servers or Linux-based runners. In this chapter, you’ll focus on:
- How GitLab CI/CD is structured
- The
.gitlab-ci.ymlfile - Runners (especially on Linux)
- Typical pipelines for Linux-based projects
- Practical examples and troubleshooting tips
This builds on general CI/CD concepts already covered in the parent chapter.
Core Concepts in GitLab CI/CD
GitLab CI/CD revolves around four main ideas:
- Pipelines – A collection of jobs executed in a defined order (stages).
- Stages – Logical phases of your pipeline (e.g.
build,test,deploy). - Jobs – Individual tasks that run scripts, usually in containers or on a Linux host.
- Runners – Agents that actually execute jobs (on VMs, containers, or bare-metal).
These are all defined and controlled by a single file in your repository: .gitlab-ci.yml.
The `.gitlab-ci.yml` File
GitLab CI/CD pipelines are configured using a YAML file stored at the root of your project:
- File name:
.gitlab-ci.yml - Purpose: Describe jobs, stages, variables, and rules for when jobs run.
- Evaluated on: Every push, merge request, or scheduled run (depending on your configuration).
Minimal Example
A very simple pipeline with build and test stages:
stages:
- build
- test
build_app:
stage: build
script:
- echo "Building on Linux..."
- make
test_app:
stage: test
script:
- echo "Running tests..."
- make testHere:
stagesdefines pipeline order: allbuildjobs run beforetestjobs.build_appandtest_appare job names.- Each job is assigned a
stageand ascript.
Common Top-Level Keys
You’ll frequently see these keys in .gitlab-ci.yml:
stages– List of stages and their order.variables– CI/CD variables available to jobs.default– Default settings for all jobs (e.g. image, before_script).workflow– Top-level control over whether a pipeline runs at all.include– Reuse config from other files or templates.
For example:
variables:
APP_ENV: "test"
BUILD_DIR: "build"
default:
image: alpine:latest
before_script:
- echo "Running on $(uname -a)"Jobs in Detail
Jobs define what actually runs on a Linux runner.
Job Structure
A job can contain:
stage– Which stage it belongs to.script– Shell commands to run.image– (For Docker runners) Which container image to use.tags– Which runners can pick up this job.only/exceptorrules– When to run the job.artifacts– What to keep after the job finishes.cache– What to cache between pipeline runs.
Example job with more options:
build_binary:
stage: build
image: gcc:13
tags: [linux, docker]
script:
- mkdir -p build
- cd build
- cmake ..
- make -j$(nproc)
artifacts:
paths:
- build/myapp
expire_in: 1 week
cache:
key: "cmake-cache"
paths:
- build/CMakeFilesHere you see several Linux-specific aspects:
- Using
gcc:13image typical for Linux builds. - Using
$(nproc)to parallelize compilation based on Linux CPU cores.
Using Stages to Structure Your Pipeline
Stages run in order; jobs in the same stage run in parallel (if enough runners are available).
Typical Stage Layout for Linux Projects
Common sequence:
lint– Code style and static checks (e.g.shellcheck,flake8).build– Compile or package binaries.test– Unit/integration tests.package– Create distributable artifacts (e.g..deb,.rpm, Docker images).deploy– Push to servers, registries, or clouds.
Example:
stages:
- lint
- build
- test
- deploy
lint_shell:
stage: lint
image: koalaman/shellcheck:stable
script:
- shellcheck scripts/*.sh
build_c_app:
stage: build
image: gcc:13
script:
- make
unit_tests:
stage: test
script:
- ./run_unit_tests.sh
deploy_to_server:
stage: deploy
script:
- ./deploy.sh
when: manual
environment:
name: productionwhen: manualrequires a human to click “Play” in GitLab before deployment starts.
GitLab Runners on Linux
Runners are the backbone of GitLab CI/CD. On Linux, you usually use:
- Docker runners – Each job runs in an isolated container. Very common.
- Shell runners – Jobs run directly on the Linux host. Useful for special hardware environments or on-prem servers.
- Kubernetes runners – Jobs are run as pods in a Kubernetes cluster.
Registering a Linux Runner (Overview)
Runner installation is typically done by an administrator on a Linux machine:
- Install the runner (e.g. Debian/Ubuntu):
curl -L --output gitlab-runner.deb \
https://gitlab-runner-downloads.s3.amazonaws.com/latest/deb/gitlab-runner_amd64.deb
sudo dpkg -i gitlab-runner.deb- Register the runner with your GitLab instance:
sudo gitlab-runner registerYou provide:
- GitLab URL
- Registration token
- Description and tags
- Executor type (
shell,docker,kubernetes, …)
- Start and enable the service:
sudo systemctl enable --now gitlab-runner
Details like managing services with systemd or installing packages are covered in other chapters; here the key point is: CI jobs execute on Linux runners that you control.
Using Tags to Target Specific Runners
Tags let you steer jobs to particular Linux runners:
- On the runner: you assign tags like
linux,docker,gpu,arm. - In the job: you reference those tags.
Example:
build_on_arm:
stage: build
tags: [arm]
script:
- uname -m
- make
Only runners tagged arm will pick this job.
Defining When Jobs Run: `only`, `except`, and `rules`
You rarely want all jobs to run on every push. GitLab provides multiple ways to control this.
Using `only` / `except`
Legacy but still common:
deploy_staging:
stage: deploy
script: ./deploy_staging.sh
only:
- main
- merge_requestsThis job only runs on:
- Commits to the
mainbranch - Merge requests (for any branch)
Using `rules` (Newer and More Flexible)
rules gives fine-grained conditions:
deploy_production:
stage: deploy
script: ./deploy_production.sh
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
- when: never- Deployment is available as a manual job only when the branch is
main. - For everything else,
when: neverprevents the job from appearing.
Common Linux-oriented conditions:
if: '$CI_COMMIT_BRANCH == "main"'– Production branch.if: '$CI_PIPELINE_SOURCE == "schedule"'– Nightly maintenance tasks on Linux servers.if: '$CI_COMMIT_TAG'– Release tags for packaging and publishing.
Artifacts and Caching for Linux Builds
Artifacts and caches help you reuse work between jobs and pipelines.
Artifacts
Artifacts are files saved after a job to be downloaded or used in later stages.
Example:
build_pkg:
stage: build
script:
- ./build_deb.sh
artifacts:
paths:
- dist/*.deb
expire_in: 3 daysThis is especially useful for:
- Compiled binaries (
ELFexecutables) .deb/.rpmpackages- Test reports or coverage data
Cache
Cache is for reusable dependency data:
install_deps:
stage: build
cache:
key: "apt-cache"
paths:
- .apt-cache
script:
- mkdir -p .apt-cache
- apt-get -o dir::cache::archives=".apt-cache" update
- apt-get -o dir::cache::archives=".apt-cache" install -y build-essentialOn Linux CI runners, caching:
- Speeds up
apt,pip,npm, orcargoinstalls - Reduces network traffic
Working with Docker in GitLab CI on Linux
Docker is often used on Linux runners to:
- Build container images
- Run tests inside containers
- Push images to GitLab’s Container Registry or Docker Hub
Building and Pushing Docker Images
Using a Docker runner with docker:dind (Docker-in-Docker):
stages:
- build
- deploy
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
build_docker_image:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
only:
- branches
deploy_with_image:
stage: deploy
script:
- ./deploy_using_docker.sh "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
when: manualThis is a common pattern for Linux-based microservices:
- Build and push container images during CI.
- Use them for deployments to Kubernetes, Docker Swarm, or individual Linux servers.
Environments and Deployments
GitLab’s Environments feature gives visibility into where your application is running (e.g. staging, production).
Environment Configuration
Example for a Linux server deployment:
deploy_staging:
stage: deploy
script:
- ./scripts/deploy_to_staging.sh
environment:
name: staging
url: https://staging.example.com
deploy_production:
stage: deploy
script:
- ./scripts/deploy_to_production.sh
environment:
name: production
url: https://example.com
when: manual
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
Your deploy_to_* scripts might:
- Use
sshto connect to Linux servers - Run
systemctlto restart services - Pull new Docker images
- Apply Kubernetes manifests
SSH, systemd, and Kubernetes are covered in other chapters; here, the key is how GitLab triggers them via CI jobs.
Reusing and Structuring CI Configuration
For larger Linux-based projects, you want to avoid repeating CI configuration.
Using `include`
You can store shared CI logic in separate files or in a “central” repository:
include:
- local: '.gitlab/ci/jobs/build.yml'
- project: 'company/shared-ci-config'
file: '/templates/linux-build.yml'This helps standardize:
- Linting jobs
- Security scans
- Build steps for similar Linux services
Extending Jobs
You can define a base job and extend it:
.linux_base:
image: alpine:latest
before_script:
- apk add --no-cache bash curl
lint:
stage: lint
extends: .linux_base
script:
- ./scripts/lint.sh
test:
stage: test
extends: .linux_base
script:
- ./scripts/test.sh
The dot-prefix (.linux_base) indicates a hidden job used only for inheritance.
Typical GitLab CI/CD Pipelines for Linux Projects
Here are a few patterns you’ll encounter often.
C/C++ Project Using `make` on Linux
stages:
- build
- test
build:
stage: build
image: gcc:13
script:
- make
artifacts:
paths:
- myprogram
test:
stage: test
script:
- ./tests/run_tests.shPython Application on Linux
stages:
- test
- package
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
test:
stage: test
image: python:3.12
cache:
paths:
- .cache/pip
script:
- pip install -r requirements.txt
- pytest
package:
stage: package
image: python:3.12
script:
- pip install build
- python -m build
artifacts:
paths:
- dist/Deploying to a Linux Server via SSH
stages:
- build
- deploy
build_binary:
stage: build
script:
- make
artifacts:
paths:
- myapp
deploy_to_server:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - >/dev/null 2>&1 || true
script:
- scp myapp user@server.example.com:/usr/local/bin/myapp
- ssh user@server.example.com 'sudo systemctl restart myapp.service'
only:
- mainHere you see GitLab CI/CD orchestrating a typical Linux deployment workflow.
Debugging GitLab CI/CD Pipelines
Debugging CI issues is a key practical skill.
Reading Job Logs
Each job has a log where you can see:
- The runner environment (Linux distribution, kernel, etc.)
- Commands being executed
- Errors (compilation errors, missing packages, permission issues)
When something fails:
- Scroll to the first occurrence of
ERROR,fatal, or a non-zero exit message. - Check any Linux-specific commands like
apt-get,yum,systemctl, etc.
Using `echo` and Verbose Flags
Increase verbosity in your scripts:
set -xto show commands as they run (inbash).- Add
-vor equivalent flags to tools (e.g.curl -v,pytest -v).
Example debug job:
debug_job:
stage: test
script:
- set -x
- uname -a
- env | sort
- ./some_script.sh
when: manual
allow_failure: trueThis is useful to inspect the Linux environment inside a CI job.
Common Linux-Specific Issues
- Missing packages – Install via
apt,yum, orapkinside the job’sscriptorbefore_script. - Permissions – CI jobs usually run as non-root users in containers; adjust file permissions or use
sudowhere appropriate (if available). - Path differences – CI runners may have different paths than your local machine (
/usr/local/bin,~/.local/bin, etc.).
Security Considerations on Linux Runners
Because jobs can execute arbitrary shell commands, you must be careful:
- Avoid using untrusted environment variables directly in shell commands.
- Do not expose sensitive CI/CD variables (e.g. SSH keys) to jobs triggered by untrusted branches or forks.
- Use protected branches and protected variables for production deployments.
- Prefer Docker runners with minimal images to reduce the attack surface on Linux hosts.
Summary
In a Linux environment, GitLab CI/CD lets you:
- Define build, test, and deployment workflows in
.gitlab-ci.yml. - Run jobs on Linux-based runners (Docker, shell, Kubernetes).
- Build and package applications for Linux (binaries, packages, containers).
- Automate deployments to Linux servers via SSH, containers, or orchestration tools.
- Reuse and structure CI configuration for multiple projects.
With these foundations, you can integrate GitLab CI/CD into almost any Linux-based development or operations workflow.