Skip to content

Gitlab CI/CD

Example Template

stages:
  - build
  - deploy

build_job:
  stage: build
  image: image:version
  script:
    - install dependencies
    - run build command
  artifacts:
    paths:
      - output_directory/
    expire_in: duration
  rules:
    - if: '$CI_COMMIT_BRANCH == "branch-name"'
      changes:
        - path/to/watched/files/**/*
    - when: never

deploy_job:
  stage: deploy
  tags: [runner-tag]
  script:
    - copy build artifacts
    - reload service
  rules:
    - if: '$CI_COMMIT_BRANCH == "branch-name"'
      changes:
        - output_directory/**/*
    - when: never

stages
Defines the order in which jobs run. Jobs in the same stage run in parallel.

build_job
A job name. Can be anything — choose something descriptive.

stage
Assigns the job to a stage. Pipeline flows stage → stage.

image:version
Docker image used to run the job. Examples: python:3.12, node:20, alpine:latest.

script
Commands executed inside the job. This is the core of what the job does.

install dependencies
Placeholder for dependency installation.
Examples: pip install -r requirements.txt, dnf install -y zip

run build command
Placeholder for your build step.
Examples: mkdocs build, npm run build, python build.py

artifacts
Files saved after the job finishes. Used by later jobs or downloadable from GitLab.

output_directory/
Folder containing build results. Examples: dist/, site/, build_output/.

duration
How long GitLab keeps artifacts. Examples: 1h, 3 days, 1 week.

rules
Controls when a job should run. Modern replacement for only/except.

if: '$CI_COMMIT_BRANCH == "branch-name"'
Runs the job only on a specific branch. Common values: main, master, develop.

changes
Runs the job only if certain files changed. Prevents unnecessary pipelines.

when: never
Default fallback: If no rule matches → job does NOT run.

tags
Selects which runner executes the job. Examples: shell, docker, prod-runner.

runner-tag
Placeholder for your runner’s tag.

example deploy script
copy build artifacts, reload service


Example CI/CD

CI/CD for my mkdocs

stages:
  - build
  - deploy

build-mkdocs:
  stage: build
  image: python:3.12
  script:
    - pip install mkdocs-material
    - cd matywaky-mkdocs
    - mkdocs build -c
  artifacts:
    paths:
      - matywaky-mkdocs/site
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      changes:
        - matywaky-mkdocs/**/*
    - when: never

deploy:
  stage: deploy
  tags: [shell]
  script:
    - TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S)
    - mkdir -p /var/www/mkdocs/releases/$TIMESTAMP
    - cp -r matywaky-mkdocs/site/* /var/www/mkdocs/releases/$TIMESTAMP/
    - rm -f /var/www/mkdocs/current
    - ln -s /var/www/mkdocs/releases/$TIMESTAMP /var/www/mkdocs/current
    - restorecon -Rv /var/www/mkdocs
    - sudo systemctl reload nginx
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      changes:
        - matywaky-mkdocs/**/*
    - when: never

Pipeline structure

The pipeline consists of two stages:

  • build — generates the MkDocs static site
  • deploy — publishes the generated site to the server

build-mkdocs job (build stage)

This job performs the following actions:

  1. Starts a Python environment
    (image: python:3.12) - provides Python and pip inside the job.
  2. Installs MkDocs Material
    (pip install mkdocs-material) - prepares the MkDocs theme and tooling.
  3. Moves into the documentation directory
    (cd matywaky-mkdocs)
  4. Builds the static documentation site
    (mkdocs build -c) - outputs HTML files into matywaky-mkdocs/site.
  5. Stores the generated site as artifacts
    (artifacts.paths: matywaky-mkdocs/site) - makes the built files available to the deploy job.
  6. Runs only when appropriate
    Controlled by rules:
    • only on the main branch
    • only when files under matywaky-mkdocs/**/* have changed
    • otherwise - when: never prevents execution
      This avoids unnecessary builds.

deploy job (deploy stage)

This job performs:

  1. Execution on a shell runner
    (tags: [shell]) - the job runs directly on the target server.
  2. Creation of a timestamped release directory
    (/var/www/mkdocs/releases/$TIMESTAMP) - each deployment becomes a separate version.
  3. Copying the built site into the new release
    (cp -r matywaky-mkdocs/site/* ...) - deploys the freshly generated HTML files.
  4. Updating the current symlink
    (ln -s /var/www/mkdocs/releases/$TIMESTAMP /var/www/mkdocs/current)- switches the live site to the new version atomically.
  5. Restoring SELinux contexts
    (restorecon -Rv /var/www/mkdocs) - required on SELinux-enabled systems.
  6. Reloading Nginx
    (systemctl reload nginx) - makes the new version available immediately.

  7. Execution only when relevant
    Same rule logic as the build job:

    • only on main
    • only when documentation files changed
    • otherwise - job is skipped

Runners

Create runner

To create new runner follow steps on image down runner-01

Configure runner

runner-02 Tags
Tags define which jobs a runner is allowed to execute.

  • Jobs specify tags in their configuration (tags: [shell])
  • Runners also have tags assigned to them
  • GitLab matches jobs to runners based on tag overlap

Purpose: To control where jobs run (e.g., Docker runner, shell runner, production runner).

Runner Description
A human‑readable label for the runner.

  • Has no functional impact
  • Used only for identification in the GitLab UI

Protected
A protected runner can only execute jobs from protected branches or protected tags.

Effect:

  • Jobs from main, master, release/* → allowed
  • Jobs from feature branches → blocked

Purpose:
To ensure that runners with access to sensitive environments (e.g., production servers) do not run untrusted code.

Lock to current project
Determines whether the runner is:

  • shared — available to multiple projects
  • locked — available only to the current project

Purpose:
To prevent other projects from using a runner that has access to sensitive resources or infrastructure.

Register runner

Install runner

Follow instruction on https://docs.gitlab.com/runner/install/

In my case (RedHat Linux VM):

curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh" -o script.rpm.sh

less script.rpm.sh

sudo bash script.rpm.sh

sudo dnf install gitlab-runner

Register runner

runner-03a

1 - choose OS
2 - copy to machine with runner

runner-03b

1 - copy command from GitLab UI and run it with sudo
2 - for GitLab.com - press Enter to accept the default
3 - human‑readable label for the runner
4 - VERY IMPORTANT! This defines how the runner will execute CI/CD jobs (shell - runs jobs directly on the machine)
5 - check list of the runners