How to Configure GitLab CI/CD Pipelines on RHEL 7

GitLab CI/CD turns every push to your repository into a fully automated pipeline that can build, test, and deploy your application without manual intervention. The pipeline definition lives in a file called .gitlab-ci.yml at the root of your repository, and the actual work is performed by GitLab Runner — a lightweight agent you install on the same RHEL 7 server as GitLab or on a dedicated build host. This guide covers the full .gitlab-ci.yml syntax, runner registration and executor types, variables, artifacts, caching, environment-based deployments, and the concept of review apps.

Prerequisites

  • GitLab CE installed and running on RHEL 7 (see “How to Install GitLab CE on RHEL 7”)
  • A GitLab project containing application source code
  • Root or sudo access on the runner host (can be the same machine as GitLab)
  • Docker installed on the runner host if you plan to use the Docker executor
  • Your application’s build tools available on the runner host (JDK, Node.js, Python, etc.)

Step 1: Install GitLab Runner on RHEL 7

GitLab Runner is distributed as a standalone binary with its own yum repository. Add the repository and install the runner package:

curl -sS https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
sudo yum install -y gitlab-runner

Enable and start the runner service:

sudo systemctl enable gitlab-runner
sudo systemctl start gitlab-runner

Verify the installed version:

gitlab-runner --version

Step 2: Register the Runner with Your GitLab Instance

A runner must be registered with GitLab before it can pick up jobs. Retrieve the registration token from your GitLab project by navigating to Settings > CI/CD > Runners > Expand and copying the token under “Set up a project runner”.

Run the interactive registration command:

sudo gitlab-runner register

The command prompts for several values:

Enter the GitLab instance URL (for example, https://gitlab.com/):
https://gitlab.example.com

Enter the registration token:
GR1348941aBcDeFgHiJkLmNoPqRsT

Enter a description for the runner:
rhel7-shell-runner

Enter tags for the runner (comma-separated):
rhel7,shell,build

Enter optional maintenance note for the runner:

Select executor: shell, docker, docker-autoscaler, instance, kubernetes, custom, docker-windows, parallels, virtualbox, docker+machine, ssh:
shell

After registration, the runner appears as available in Settings > CI/CD > Runners with a green circle. Restart the runner service to ensure the new configuration takes effect:

sudo systemctl restart gitlab-runner

Step 3: Understand Executor Types

The shell executor runs jobs directly in a shell on the runner host using the system’s installed tools. It is the simplest option but means all jobs share the host environment. The Docker executor runs each job inside a fresh Docker container, providing full isolation and reproducibility. Configure the Docker executor during registration or by editing /etc/gitlab-runner/config.toml:

[runners.docker]
  image = "alpine:latest"
  privileged = false
  volumes = ["/cache"]
  shm_size = 0

For the Docker executor, install Docker on the runner host:

sudo yum install -y yum-utils
sudo yum-config-manager --add-repo 
    https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io
sudo systemctl enable docker
sudo systemctl start docker
sudo usermod -aG docker gitlab-runner

Step 4: Write Your First .gitlab-ci.yml

Create a .gitlab-ci.yml file at the root of your repository. The following example is a complete pipeline for a Node.js application:

stages:
  - build
  - test
  - deploy

variables:
  NODE_ENV: "production"
  APP_PORT: "3000"

build_job:
  stage: build
  image: node:18-alpine
  script:
    - npm ci --prefer-offline
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour

test_job:
  stage: test
  image: node:18-alpine
  script:
    - npm ci --prefer-offline
    - npm run test -- --coverage
  coverage: '/Liness*:s*(d+.?d*)%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

deploy_production:
  stage: deploy
  script:
    - echo "Deploying to production server..."
    - rsync -avz --delete dist/ [email protected]:/var/www/myapp/
  environment:
    name: production
    url: https://myapp.example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Step 5: Use Variables and Protect Secrets

GitLab CI/CD variables fall into two categories: those defined in .gitlab-ci.yml and those stored securely in the GitLab UI. For sensitive data such as deployment SSH keys, API tokens, and database passwords, always use the UI: navigate to Settings > CI/CD > Variables > Add variable, enable Masked to hide the value in job logs, and enable Protected to restrict it to protected branches.

Reference variables in the pipeline with the $VARIABLE_NAME syntax:

deploy_job:
  stage: deploy
  script:
    - curl -X POST 
        -H "Authorization: Bearer $DEPLOY_API_TOKEN" 
        -d '{"ref":"main"}' 
        "https://api.example.com/trigger-deploy"

GitLab also exposes a rich set of predefined variables automatically:

CI_COMMIT_SHA        # Full commit hash
CI_COMMIT_SHORT_SHA  # 8-character short hash
CI_COMMIT_BRANCH     # Branch name
CI_PIPELINE_ID       # Unique pipeline ID
CI_PROJECT_NAME      # Repository name
CI_REGISTRY          # GitLab container registry URL
CI_REGISTRY_USER     # Registry login username
CI_REGISTRY_PASSWORD # Registry login password (ephemeral token)

Step 6: Configure Artifacts

Artifacts are files generated by a job that GitLab preserves and makes available for download or for use by later pipeline stages:

build_app:
  stage: build
  script:
    - mvn clean package -DskipTests
  artifacts:
    name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
    paths:
      - target/*.jar
      - target/*.war
    exclude:
      - target/*-sources.jar
    expire_in: 7 days
    when: on_success

For test reports, use the reports key so GitLab can parse and display results in the merge request interface:

  artifacts:
    reports:
      junit: target/surefire-reports/TEST-*.xml

Step 7: Configure Caching

Caching persists directories between pipeline runs to avoid re-downloading dependencies. Unlike artifacts, cache is not guaranteed to exist — pipelines must still work if the cache is empty:

variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

cache:
  key:
    files:
      - requirements.txt
  paths:
    - .cache/pip
    - venv/

build:
  stage: build
  script:
    - python -m venv venv
    - source venv/bin/activate
    - pip install -r requirements.txt

For Node.js projects, cache the node_modules directory keyed on package-lock.json:

cache:
  key:
    files:
      - package-lock.json
  paths:
    - node_modules/

Step 8: Define Pipeline Triggers and Rules

The rules keyword provides fine-grained control over when a job runs, replacing the older only/except syntax:

deploy_staging:
  stage: deploy
  script:
    - ./deploy.sh staging
  rules:
    # Run on any push to a branch other than main
    - if: $CI_COMMIT_BRANCH != "main" && $CI_PIPELINE_SOURCE == "push"

deploy_production:
  stage: deploy
  script:
    - ./deploy.sh production
  rules:
    # Run only on main branch pushes
    - if: $CI_COMMIT_BRANCH == "main"
    # Also run if triggered manually via API
    - if: $CI_PIPELINE_SOURCE == "api"

nightly_build:
  stage: build
  script:
    - make all
  rules:
    # Run only when triggered by a schedule
    - if: $CI_PIPELINE_SOURCE == "schedule"

Create scheduled pipelines in GitLab under CI/CD > Schedules using standard cron syntax.

Step 9: Deploy to Environments

The environment keyword links a deployment job to a named environment, enabling GitLab to track what version is deployed where and providing a rollback button in the UI:

deploy_staging:
  stage: deploy
  script:
    - ssh [email protected] "cd /app && git pull && systemctl restart app"
  environment:
    name: staging
    url: https://staging.example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"

deploy_production:
  stage: deploy
  script:
    - ssh [email protected] "cd /app && git pull && systemctl restart app"
  environment:
    name: production
    url: https://myapp.example.com
  when: manual
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Setting when: manual on the production deployment creates a one-click deploy button in the GitLab pipeline UI, preventing automatic deployments to production while still making them quick and traceable.

Step 10: Understand Review Apps

Review apps automatically deploy each merge request to a temporary, isolated environment, giving reviewers a live preview of the changes. GitLab manages the lifecycle — creating the environment when the MR opens and tearing it down when it is merged or closed.

review:
  stage: deploy
  script:
    - export REVIEW_DOMAIN="mr-${CI_MERGE_REQUEST_IID}.review.example.com"
    - ./deploy-review.sh "$REVIEW_DOMAIN"
  environment:
    name: review/$CI_MERGE_REQUEST_IID
    url: https://mr-$CI_MERGE_REQUEST_IID.review.example.com
    on_stop: stop_review
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

stop_review:
  stage: deploy
  script:
    - ./teardown-review.sh "$CI_MERGE_REQUEST_IID"
  environment:
    name: review/$CI_MERGE_REQUEST_IID
    action: stop
  when: manual
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

The on_stop key links the cleanup job so GitLab calls it automatically when the merge request is closed.

Conclusion

GitLab CI/CD pipelines defined in .gitlab-ci.yml provide a powerful, version-controlled approach to automated software delivery. You now have a working runner registered to your RHEL 7 GitLab instance and a comprehensive understanding of stages, jobs, variables, artifacts, caching, rules-based triggers, and environment-tracked deployments. With review apps, every merge request can have its own live deployment, dramatically shortening the feedback loop between writing code and verifying it in a real environment. The next step is tuning runner capacity — consider adding additional runners on dedicated build hosts to parallelize heavy pipelines and reduce queue times.