GitLab CI/CD is GitLab’s built-in continuous integration and delivery system that automatically runs pipelines when code is pushed to a repository. Unlike Jenkins (which requires a separate server and complex plugin configuration), GitLab CI/CD is fully integrated with the GitLab platform — pipelines are defined in a .gitlab-ci.yml file in the repository root and run automatically on GitLab Runners (agents that can be installed on any server). GitLab CI/CD provides an auto-cancellation of redundant pipelines, parallel job execution, Docker-based job isolation, caching, artifacts, and environments for deployment tracking. This guide covers writing .gitlab-ci.yml pipelines on RHEL 9 for build, test, and Docker deployment workflows, and registering a GitLab Runner on RHEL 9.

Prerequisites

  • GitLab CE or GitLab.com account
  • RHEL 9 server for the GitLab Runner

Step 1 — Install and Register a GitLab Runner

# Install GitLab Runner on RHEL 9
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | bash
dnf install -y gitlab-runner

# Register the runner with your GitLab instance
# Get the registration token from: GitLab → Project → Settings → CI/CD → Runners
gitlab-runner register 
    --non-interactive 
    --url https://gitlab.example.com 
    --registration-token YOUR_TOKEN 
    --executor docker 
    --docker-image alpine:latest 
    --description "RHEL9 Docker Runner" 
    --tag-list "docker,rhel9"

systemctl enable --now gitlab-runner

Step 2 — Basic .gitlab-ci.yml

# .gitlab-ci.yml — placed in repository root
stages:
  - install
  - test
  - build
  - deploy

variables:
  NODE_ENV: test
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

install:
  stage: install
  image: node:20-alpine
  script:
    - npm ci
  cache:
    key: $CI_COMMIT_REF_SLUG
    paths:
      - node_modules/

test:
  stage: test
  image: node:20-alpine
  script:
    - npm test
  artifacts:
    reports:
      junit: test-results/*.xml
    when: always

build-image:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE
  only:
    - main

deploy:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/myapp myapp=$DOCKER_IMAGE -n production
    - kubectl rollout status deployment/myapp -n production
  environment:
    name: production
    url: https://myapp.example.com
  only:
    - main

Step 3 — Using GitLab CI/CD Variables (Secrets)

# Store secrets in: Project → Settings → CI/CD → Variables
# Examples: DOCKER_PASSWORD, KUBECONFIG, SSH_PRIVATE_KEY

# Access in .gitlab-ci.yml:
deploy:
  script:
    - echo "$KUBECONFIG" | base64 -d > /tmp/kubeconfig
    - kubectl --kubeconfig=/tmp/kubeconfig apply -f k8s/
    - rm /tmp/kubeconfig  # Clean up credentials

Step 4 — Pipeline Rules and Triggers

# Conditional job execution with 'rules':
deploy:
  rules:
    - if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
      when: on_success
    - if: '$CI_COMMIT_TAG =~ /^vd+.d+.d+$/'  # Trigger on version tags
      when: manual  # Require manual approval for production

Conclusion

GitLab CI/CD on RHEL 9 provides a tightly integrated pipeline system that eliminates the need for a separate CI server. The built-in GitLab Container Registry ($CI_REGISTRY) with pre-populated $CI_REGISTRY_USER and $CI_REGISTRY_PASSWORD variables means Docker image push pipelines require zero credential configuration — GitLab handles authentication automatically. The cache configuration for node_modules/ is the single biggest pipeline speed improvement for Node.js projects, reducing install stage time from 60+ seconds to under 5 seconds for subsequent pipeline runs.

Next steps: How to Install GitLab CE on RHEL 9, How to Install Jenkins on RHEL 9, and How to Set Up a Git Server with Gitea on RHEL 9.