How to Set Up a Complete DevSecOps Pipeline on RHEL 7

DevSecOps integrates security testing directly into every phase of the software delivery lifecycle rather than treating it as a final gate before production. The goal is to detect vulnerabilities, exposed secrets, and misconfigurations early — when they are cheap to fix — and to prove compliance automatically on every commit. This tutorial builds a complete, production-grade DevSecOps pipeline on RHEL 7, connecting Gitea (source control), Jenkins (CI/CD orchestration), SonarQube (static application security testing), Trivy (container image scanning), Harbor (secure container registry), and ArgoCD (GitOps continuous delivery to Kubernetes). Each tool is connected by a Jenkinsfile that models a real pipeline, with OWASP checks, DAST scanning via OWASP ZAP, and OPA/Gatekeeper policy enforcement.

Prerequisites

  • RHEL 7 host with at least 8 GB RAM and 4 CPU cores (16 GB recommended for running all tools locally).
  • Docker or Podman installed (sudo yum install -y docker; start with sudo systemctl enable --now docker).
  • A Kubernetes cluster reachable from this host (can be a local k3s/minikube cluster).
  • Java 11 installed for Jenkins and SonarQube: sudo yum install -y java-11-openjdk.
  • Basic knowledge of Git, Docker, and Kubernetes.

Step 1: Pipeline Architecture Overview

The complete pipeline flows as follows:

  1. Developer pushes code to a Gitea repository.
  2. Gitea webhook triggers a Jenkins build.
  3. Jenkins Stage: Build — compiles the application and runs unit tests.
  4. Jenkins Stage: SAST — SonarQube scans source code for vulnerabilities and code smells.
  5. Jenkins Stage: OWASP Dependency Check — identifies known CVEs in third-party dependencies.
  6. Jenkins Stage: Build Image — builds the Docker container image.
  7. Jenkins Stage: Container Scan — Trivy scans the image for OS and library CVEs.
  8. Jenkins Stage: Push to Harbor — pushes the signed image to the Harbor registry.
  9. Jenkins Stage: DAST — OWASP ZAP scans the running application in a staging environment.
  10. Jenkins Stage: Deploy via ArgoCD — ArgoCD syncs the new image tag to Kubernetes.
  11. OPA/Gatekeeper enforces admission policies on every Kubernetes resource.
  12. Slack notifications alert the team on any failure.
  13. Grafana dashboard visualises pipeline metrics exported by Jenkins.

Step 2: Install and Configure Gitea

Gitea is a lightweight self-hosted Git service. Install it as a systemd service:

sudo yum install -y git

# Create a dedicated user
sudo useradd -r -m -d /home/gitea -s /bin/bash gitea

# Download binary
sudo curl -Lo /usr/local/bin/gitea 
  https://github.com/go-gitea/gitea/releases/download/v1.21.11/gitea-1.21.11-linux-amd64
sudo chmod +x /usr/local/bin/gitea

sudo mkdir -p /var/lib/gitea/{custom,data,log}
sudo chown -R gitea:gitea /var/lib/gitea
sudo mkdir -p /etc/gitea
sudo chown root:gitea /etc/gitea
sudo chmod 770 /etc/gitea

Create the systemd unit:

sudo vi /etc/systemd/system/gitea.service
[Unit]
Description=Gitea
After=network.target

[Service]
User=gitea
Group=gitea
WorkingDirectory=/var/lib/gitea
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now gitea

Complete the web-based installation at http://<server-ip>:3000. Create an organisation and a repository named myapp. Under repository Settings > Webhooks, add a webhook pointing to http://<jenkins-ip>:8080/github-webhook/.

Step 3: Install Jenkins

sudo wget -O /etc/yum.repos.d/jenkins.repo 
  https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key

sudo yum install -y jenkins
sudo systemctl enable --now jenkins

Retrieve the initial admin password:

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

Complete the Jenkins setup wizard at http://<server-ip>:8080. Install these plugins: Pipeline, Git, SonarQube Scanner, OWASP Dependency-Check, Docker Pipeline, Slack Notification, Prometheus Metrics.

Step 4: Deploy SonarQube

SonarQube requires Elasticsearch’s virtual memory setting:

echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

sudo docker run -d 
  --name sonarqube 
  -p 9000:9000 
  -e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true 
  sonarqube:9-community

Access SonarQube at http://<server-ip>:9000 (default credentials: admin/admin). Generate a project token under My Account > Security. Store it as a Jenkins credential with ID sonarqube-token.

In Jenkins > Manage Jenkins > Configure System, add the SonarQube server URL and the credential token under the SonarQube section.

Step 5: Deploy Harbor Registry

curl -Lo /tmp/harbor.tar.gz 
  https://github.com/goharbor/harbor/releases/download/v2.10.2/harbor-offline-installer-v2.10.2.tgz
tar xzf /tmp/harbor.tar.gz -C /opt/
cd /opt/harbor
cp harbor.yml.tmpl harbor.yml
vi harbor.yml

Edit these fields in harbor.yml:

hostname: harbor.lab.local
http:
  port: 5000
harbor_admin_password: Harbor12345!
sudo ./install.sh

Log in from Docker and create a project named myapp:

docker login harbor.lab.local:5000 -u admin -p Harbor12345!

Step 6: The Jenkinsfile

Create a Jenkinsfile in the root of the Gitea repository:

pipeline {
    agent any

    environment {
        APP_NAME     = 'myapp'
        IMAGE_TAG    = "${env.BUILD_NUMBER}"
        HARBOR_CREDS = credentials('harbor-credentials')
        REGISTRY     = 'harbor.lab.local:5000'
        SLACK_CHANNEL = '#devsecops-alerts'
    }

    stages {

        stage('Checkout') {
            steps {
                git url: 'http://gitea.lab.local:3000/org/myapp.git',
                    credentialsId: 'gitea-credentials'
            }
        }

        stage('Build and Unit Tests') {
            steps {
                sh 'mvn clean package -DskipTests=false'
            }
            post {
                always {
                    junit 'target/surefire-reports/*.xml'
                }
            }
        }

        stage('SAST - SonarQube') {
            steps {
                withSonarQubeEnv('SonarQube') {
                    sh '''
                        mvn sonar:sonar 
                          -Dsonar.projectKey=${APP_NAME} 
                          -Dsonar.host.url=http://sonarqube.lab.local:9000 
                          -Dsonar.login=${SONAR_AUTH_TOKEN}
                    '''
                }
                timeout(time: 5, unit: 'MINUTES') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }

        stage('OWASP Dependency Check') {
            steps {
                dependencyCheck additionalArguments: '''
                    --scan ./
                    --format HTML
                    --format XML
                    --out ./dependency-check-report
                    --failOnCVSS 7
                ''', odcInstallation: 'OWASP-DC'
                dependencyCheckPublisher pattern: 'dependency-check-report/dependency-check-report.xml'
            }
        }

        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${REGISTRY}/${APP_NAME}:${IMAGE_TAG} ."
            }
        }

        stage('Container Scan - Trivy') {
            steps {
                sh '''
                    trivy image 
                      --exit-code 1 
                      --severity HIGH,CRITICAL 
                      --format table 
                      ${REGISTRY}/${APP_NAME}:${IMAGE_TAG}
                '''
            }
        }

        stage('Push to Harbor') {
            steps {
                sh '''
                    echo ${HARBOR_CREDS_PSW} | 
                      docker login ${REGISTRY} -u ${HARBOR_CREDS_USR} --password-stdin
                    docker push ${REGISTRY}/${APP_NAME}:${IMAGE_TAG}
                '''
            }
        }

        stage('DAST - OWASP ZAP') {
            steps {
                sh '''
                    docker run --rm 
                      -v $(pwd)/zap-reports:/zap/wrk 
                      owasp/zap2docker-stable zap-baseline.py 
                        -t http://staging.lab.local:8080 
                        -r zap-report.html 
                        -x zap-report.xml 
                        -I
                '''
                publishHTML target: [
                    reportDir: 'zap-reports',
                    reportFiles: 'zap-report.html',
                    reportName: 'ZAP DAST Report'
                ]
            }
        }

        stage('Deploy - ArgoCD Sync') {
            steps {
                sh '''
                    argocd app set ${APP_NAME} 
                      --helm-set image.tag=${IMAGE_TAG}
                    argocd app sync ${APP_NAME} 
                      --prune 
                      --force
                    argocd app wait ${APP_NAME} 
                      --timeout 120
                '''
            }
        }
    }

    post {
        failure {
            slackSend(
                channel: env.SLACK_CHANNEL,
                color: 'danger',
                message: "PIPELINE FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}n${env.BUILD_URL}"
            )
        }
        success {
            slackSend(
                channel: env.SLACK_CHANNEL,
                color: 'good',
                message: "Pipeline passed: ${env.JOB_NAME} #${env.BUILD_NUMBER} deployed image tag ${IMAGE_TAG}"
            )
        }
    }
}

Step 7: Install Trivy for Container Scanning

sudo rpm -ivh https://github.com/aquasecurity/trivy/releases/download/v0.51.1/trivy_0.51.1_Linux-64bit.rpm

# Update the vulnerability database
trivy image --download-db-only

# Test a scan manually
trivy image --severity HIGH,CRITICAL nginx:latest

Step 8: OPA Gatekeeper Policy Enforcement

Install OPA Gatekeeper on your Kubernetes cluster to enforce admission policies. Example: require all containers to run as non-root:

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.15/deploy/gatekeeper.yaml

Create a ConstraintTemplate:

cat <<EOF | kubectl apply -f -
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequirenonroot
spec:
  crd:
    spec:
      names:
        kind: K8sRequireNonRoot
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequirenonroot
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.securityContext.runAsNonRoot
          msg := sprintf("Container %v must set runAsNonRoot: true", [container.name])
        }
EOF

Apply the constraint to all namespaces:

cat <<EOF | kubectl apply -f -
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireNonRoot
metadata:
  name: require-non-root
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
EOF

Step 9: Pipeline Metrics in Grafana

The Jenkins Prometheus Metrics plugin exposes a /prometheus endpoint. Add it as a Prometheus scrape target:

# In prometheus.yml scrape_configs:
- job_name: 'jenkins'
  metrics_path: '/prometheus'
  static_configs:
    - targets: ['jenkins.lab.local:8080']

Install the Jenkins: Performance and Health Overview dashboard from Grafana.com (dashboard ID 9524) to visualise build success rates, queue depth, executor utilisation, and pipeline duration trends.

Conclusion

You have constructed an end-to-end DevSecOps pipeline on RHEL 7 that enforces security at every stage: SonarQube catches insecure code patterns before a container is ever built, OWASP Dependency Check identifies vulnerable libraries in the build artefact, Trivy blocks images with critical CVEs from reaching the registry, ZAP tests the running application for OWASP Top 10 vulnerabilities, and OPA Gatekeeper rejects any Kubernetes workload that violates security policies at admission time. Slack notifications give the team immediate visibility into failures, and Grafana provides the pipeline health trend data needed for engineering metrics reviews. This architecture shifts security left without slowing developers down — every gate is automated, reproducible, and provides actionable evidence for compliance audits.