GitLab CI/CD is built directly into the GitLab platform, making it straightforward to automate builds, tests, and deployments without installing a separate CI server. Pipelines are defined in a .gitlab-ci.yml file committed to your repository, and they execute on GitLab Runners — lightweight agents that can run on bare metal, virtual machines, or inside Docker containers. This tutorial explains the structure of a .gitlab-ci.yml file, how to register a Runner on RHEL 8, and how to build a pipeline that progresses from build through test to deployment over SSH.
Prerequisites
- GitLab CE installed and accessible on RHEL 8 (see the GitLab CE installation tutorial)
- A GitLab project with at least one commit in the default branch
- A second RHEL 8 server or the same GitLab host to act as the Runner
- Docker installed on the Runner host if using the Docker executor
Step 1 — Understand the .gitlab-ci.yml Structure
The .gitlab-ci.yml file lives at the root of your repository. It defines global settings, the list of pipeline stages, and individual jobs.
stages:
- build
- test
- deploy
variables:
APP_NAME: my-app
DEPLOY_HOST: deploy.example.com
default:
image: maven:3.9-eclipse-temurin-11
build-job:
stage: build
script:
- mvn clean package -DskipTests
artifacts:
paths:
- target/*.jar
expire_in: 1 hour
test-job:
stage: test
script:
- mvn test
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .m2/repository
deploy-job:
stage: deploy
script:
- echo "Deploying $APP_NAME to $DEPLOY_HOST"
- bash deploy.sh
only:
- main
Jobs in the same stage run in parallel by default. Jobs in later stages run only after all jobs in the previous stage succeed. The artifacts keyword passes build outputs between stages.
Step 2 — Install and Register a GitLab Runner
GitLab Runner is a separate binary that connects to your GitLab instance and picks up pipeline jobs. Install it on the server that will execute your CI jobs.
# Add the GitLab Runner repository
curl -sS https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
# Install the runner package
sudo dnf install -y gitlab-runner
# Enable and start the service
sudo systemctl enable --now gitlab-runner
# Confirm it is running
sudo systemctl status gitlab-runner
Now register the Runner against your GitLab instance. You will need the registration token from Settings → CI/CD → Runners in your GitLab project.
sudo gitlab-runner register
--non-interactive
--url "http://gitlab.example.com"
--registration-token "YOUR_REGISTRATION_TOKEN"
--executor "shell"
--description "rhel8-shell-runner"
--tag-list "rhel8,shell"
--run-untagged true
Use --executor docker --docker-image maven:3.9-eclipse-temurin-11 instead for the Docker executor, which runs each job in a fresh container for better isolation.
Step 3 — Configure CI/CD Variables in the GitLab UI
Sensitive values like SSH private keys, API tokens, or database passwords should be stored as masked CI/CD variables rather than in the YAML file.
# In GitLab UI: Project → Settings → CI/CD → Variables → Add variable
#
# Example variables to add:
# Key: SSH_PRIVATE_KEY Type: File Masked: yes Protected: yes
# Key: DEPLOY_USER Type: Variable Masked: no Value: deployer
# Key: DEPLOY_HOST Type: Variable Masked: no Value: 192.168.1.50
#
# Reference them in .gitlab-ci.yml:
# script:
# - eval $(ssh-agent -s)
# - ssh-add "$SSH_PRIVATE_KEY"
# - ssh $DEPLOY_USER@$DEPLOY_HOST "cd /opt/app && git pull && systemctl restart app"
Variables marked Protected are only injected into pipelines running on protected branches, adding an extra safeguard for production deployments.
Step 4 — Deploy to a Remote Server via SSH
A common deployment pattern is to SSH from the CI Runner into a target server and run deployment commands. Configure this in the deploy stage of your .gitlab-ci.yml.
deploy-job:
stage: deploy
before_script:
- apt-get update -y && apt-get install -y openssh-client || true
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d 'r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
script:
- ssh "$DEPLOY_USER@$DEPLOY_HOST" "
set -e
cd /opt/$APP_NAME
git pull origin main
mvn clean package -DskipTests
sudo systemctl restart $APP_NAME
"
only:
- main
environment:
name: production
url: http://$DEPLOY_HOST
The environment keyword creates a tracked deployment environment in GitLab, enabling rollback and deployment history visible under Deployments → Environments.
Step 5 — Trigger and Monitor Pipeline Runs
Pipelines trigger automatically on every push to the repository. You can also trigger them manually or via the GitLab API.
# Trigger pipeline via GitLab API
curl -X POST
--fail
-F token=YOUR_PIPELINE_TRIGGER_TOKEN
-F ref=main
http://gitlab.example.com/api/v4/projects/PROJECT_ID/trigger/pipeline
# Check Runner logs on the host if a job stays pending
sudo journalctl -u gitlab-runner -f
# View registered runners and their status
sudo gitlab-runner list
In the GitLab UI, navigate to CI/CD → Pipelines to see the status of each stage. Click any job to view its real-time console log, making it easy to diagnose failures.
Conclusion
You have defined a multi-stage GitLab CI/CD pipeline in .gitlab-ci.yml, registered a GitLab Runner on RHEL 8, secured credentials with masked CI/CD variables, and implemented SSH-based deployment to a target server. GitLab’s pipeline architecture scales from simple single-stage builds to complex multi-project pipelines with DAG dependencies and dynamic child pipelines. As your team grows, consider creating shared Runner pools, adding Docker-in-Docker builds for containerized applications, and integrating GitLab’s built-in container registry to store and deploy images.
Next steps: How to Install Jenkins on RHEL 8, How to Configure Jenkins Pipelines and Jenkinsfile on RHEL 8, and How to Set Up a Git Server with Gitea on RHEL 8.