Kahibaro
Discord Login Register

6.2.4 GitLab CI/CD

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:

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:

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:

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 test

Here:

Common Top-Level Keys

You’ll frequently see these keys in .gitlab-ci.yml:

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:

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/CMakeFiles

Here you see several Linux-specific aspects:

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:

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: production

GitLab Runners on Linux

Runners are the backbone of GitLab CI/CD. On Linux, you usually use:

Registering a Linux Runner (Overview)

Runner installation is typically done by an administrator on a Linux machine:

  1. 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
  1. Register the runner with your GitLab instance:
   sudo gitlab-runner register

You provide:

  1. 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:

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_requests

This job only runs on:

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

Common Linux-oriented conditions:

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 days

This is especially useful for:

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-essential

On Linux CI runners, caching:

Working with Docker in GitLab CI on Linux

Docker is often used on Linux runners to:

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: manual

This is a common pattern for Linux-based microservices:

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:

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:

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.sh

Python 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:
    - main

Here 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:

When something fails:

Using `echo` and Verbose Flags

Increase verbosity in your scripts:

Example debug job:

debug_job:
  stage: test
  script:
    - set -x
    - uname -a
    - env | sort
    - ./some_script.sh
  when: manual
  allow_failure: true

This is useful to inspect the Linux environment inside a CI job.

Common Linux-Specific Issues

Security Considerations on Linux Runners

Because jobs can execute arbitrary shell commands, you must be careful:

Summary

In a Linux environment, GitLab CI/CD lets you:

With these foundations, you can integrate GitLab CI/CD into almost any Linux-based development or operations workflow.

Views: 112

Comments

Please login to add a comment.

Don't have an account? Register now!