Stack
- Node.js
- GitHub
- Jenkins
- CodeRabbit
- GitHub Copilot
- Docker
- K3s
- Ubuntu 22.04 LTS
Table of Contents
- Prerequisites and Environment Assumptions
- Architecture Overview
- System Preparation
- Setting Up CodeRabbit for AI PR Reviews
- Configuring GitHub Copilot in the Pipeline
- Installing and Configuring Jenkins
- Integrating AI Review Gates into the Jenkinsfile
- Blocking Merges on AI Review Failures
- Docker Build and Push Stage
- Deploying to K3s
- Using the GitHub API to Parse AI Review Signals
- Secrets Management
- Full Pipeline Execution Walkthrough
- Troubleshooting Reference
Prerequisites and Environment Assumptions
What You Need to Know Before Starting
- Solid Jenkins pipeline (Groovy DSL) experience
- Familiarity with GitHub Apps, webhooks, and the GitHub REST API
- Working knowledge of Kubernetes Deployments and rolling update mechanics
- Understanding of PR-based Git workflows (feature branches, protected branches, required status checks)
- Basic shell scripting for API response parsing with
jq
Infrastructure Assumptions
- Two Ubuntu 22.04 LTS servers:
- CI server: Jenkins + Docker, 4 vCPU / 8 GB RAM minimum
- K3s node: Kubernetes cluster, 2 vCPU / 4 GB RAM minimum
- GitHub repository with branch protection rules enabled on
main - Docker Hub account for image registry
- CodeRabbit account (free tier covers open-source repos; paid tier for private)
- GitHub Copilot subscription (individual or business)
- A domain name with DNS control for Ingress/TLS
Required Software Versions
| Tool | Version |
|---|---|
| Ubuntu | 22.04 LTS |
| Jenkins | 2.452.x LTS |
| Docker Engine | 26.x |
| K3s | v1.30.x |
| kubectl | matching K3s minor |
| jq | 1.6+ |
| Node.js | 20.x LTS |
Architecture Overview
The core difference from a conventional pipeline is the introduction of AI review gates between code push and deployment. The pipeline does not proceed to build or deploy until AI-generated review signals are resolved.
Developer pushes feature branch to GitHub
│
▼
Developer opens Pull Request
│
├──► CodeRabbit analyzes PR automatically
│ - Line-by-line code review
│ - Security and logic issue flagging
│ - Summary comment posted to PR
│
├──► GitHub Copilot suggestions surfaced in editor (pre-push)
│
▼
Jenkins pipeline triggered on PR creation/update
│
├── Stage: Checkout
├── Stage: Install & Lint
├── Stage: Tests
├── Stage: AI Review Gate (poll GitHub API for CodeRabbit verdict)
│ └── BLOCK if AI flagged critical issues
├── Stage: Build Docker Image
├── Stage: Push to Registry
└── Stage: Deploy to K3s (only on merge to main)
AI-assisted DevOps pipeline automation happens asynchronously via GitHub. Jenkins polls the GitHub API to read CodeRabbit’s review state before allowing the pipeline to continue past the gate stage.
System Preparation
Run on both servers unless noted.
Update and Install Base Packages
sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y \
curl \
gnupg \
ca-certificates \
lsb-release \
apt-transport-https \
software-properties-common \
git \
jq \
unzip \
ufw
Configure UFW
On the CI server:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 8080/tcp
sudo ufw allow 50000/tcp
sudo ufw enableOn the K3s node:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 6443/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 10250/tcp
sudo ufw enableInstall Docker on the CI Server
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-pluginSetting Up CodeRabbit for AI PR Reviews
CodeRabbit is a GitHub App that performs automatic AI-powered code review on every pull request. It posts inline comments and a summary review, and can be configured to approve or request changes.
Install the CodeRabbit GitHub App
- Navigate to coderabbit.ai and sign in with your GitHub account.
- Click Add to GitHub and select the repository or organization.
- Grant the requested permissions:
- Read access to code, pull requests, issues
- Write access to pull request reviews and comments
- Complete the installation. CodeRabbit will now automatically trigger on every new PR in the selected repositories.
Configure CodeRabbit via .coderabbit.yaml
CodeRabbit’s behavior is controlled by a config file committed to your repository root. Create .coderabbit.yaml:
# .coderabbit.yaml
version: "2"
reviews:
# Automatically request changes when critical issues are found
request_changes_workflow: true
# Set review profile: chill (lenient) | assertive (strict) | informative (comments only)
profile: "assertive"
# Review all changed files, not just the diff
review_status: true
# Post a high-level walkthrough comment on every PR
high_level_summary: true
# Enable path-specific review instructions
path_instructions:
- path: "src/**/*.js"
instructions: |
Focus on: security vulnerabilities, async/await error handling,
input validation, and N+1 query patterns.
- path: "k8s/**/*.yaml"
instructions: |
Check for: missing resource limits, missing readiness probes,
use of latest image tags, and overly permissive RBAC.
- path: "Dockerfile"
instructions: |
Check for: non-root user enforcement, pinned base image tags,
secrets in ENV instructions, and unnecessary packages.
# Files to exclude from AI review
path_filters:
- "!package-lock.json"
- "!**/*.test.js"
- "!coverage/**"
chat:
# Allow team members to ask CodeRabbit follow-up questions in PR comments
auto_reply: trueCommit and push this file:
git add .coderabbit.yaml
git commit -m "chore: add CodeRabbit configuration"
git push origin mainVerify CodeRabbit Is Working
Open a test pull request with a deliberate issue (e.g., an unhandled promise rejection or a hardcoded credential string). Within 1-3 minutes, CodeRabbit should post:
- An inline comment on the offending line
- A PR-level summary comment
- A review with Changes Requested status if the
assertiveprofile detected issues
Check the PR’s Checks tab to confirm the CodeRabbit review status is being reported as a GitHub check.
Configuring GitHub Copilot in the Pipeline
GitHub Copilot operates primarily in the developer’s editor (VS Code, JetBrains, Neovim). Its role in the pipeline is enforced through pre-push hooks and team workflow standards rather than direct CI integration. Here is how to operationalize it.
Enable Copilot for Your Organization
Go to your GitHub organization: Settings > Copilot > Access. Enable Copilot for all members or specific teams. Seat assignment takes effect immediately.
Install Copilot CLI on the CI Server (Optional Linting Aid)
GitHub Copilot CLI provides AI-powered command suggestions. While primarily a developer tool, it can be used in CI to explain failing test output or suggest fixes in automated comments.
# Install GitHub CLI first
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
| sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] \
https://cli.github.com/packages stable main" \
| sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt-get update
sudo apt-get install -y gh
# Authenticate (use a service account token for CI)
echo "<GITHUB_TOKEN>" | gh auth login --with-token
# Install Copilot CLI extension
gh extension install github/gh-copilot
gh copilot --versionEnforce Copilot Usage via Pre-Push Hook (Developer Workstations)
Distribute this hook via your repo’s scripts/ directory and document it in the README as a required setup step.
Create scripts/install-hooks.sh:
#!/bin/bash
# Run once after cloning: bash scripts/install-hooks.sh
HOOK_DIR="$(git rev-parse --git-dir)/hooks"
cat > "$HOOK_DIR/pre-push" << 'EOF'
#!/bin/bash
# Remind developers to address Copilot suggestions before pushing
echo ""
echo "Pre-push checklist:"
echo " [ ] Copilot suggestions reviewed and addressed"
echo " [ ] No hardcoded secrets or credentials"
echo " [ ] Unit tests updated for changed functions"
echo ""
read -p "Continue push? (y/n): " confirm
[ "$confirm" = "y" ] || exit 1
EOF
chmod +x "$HOOK_DIR/pre-push"
echo "Git hooks installed."chmod +x scripts/install-hooks.sh
git add scripts/install-hooks.sh
git commit -m "chore: add git hooks installer"Installing and Configuring Jenkins
Install Java
sudo apt-get install -y openjdk-21-jdk
java -version
echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64' | sudo tee -a /etc/environment
source /etc/environmentInstall Jenkins
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key \
| sudo tee /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
https://pkg.jenkins.io/debian-stable binary/" \
| sudo tee /etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt-get update
sudo apt-get install -y jenkins
sudo systemctl enable jenkins
sudo systemctl start jenkins
sudo systemctl status jenkinsRetrieve Initial Admin Password
sudo cat /var/lib/jenkins/secrets/initialAdminPasswordNavigate to http://<ci-server-ip>:8080, complete the setup wizard, and install suggested plugins.
Install Additional Plugins
curl -O http://localhost:8080/jnlpJars/jenkins-cli.jar
java -jar jenkins-cli.jar \
-s http://localhost:8080 \
-auth admin:<admin-password> \
install-plugin \
docker-workflow \
github \
credentials-binding \
http_request \
--restartThe http_request plugin is used to call the GitHub API from within the Jenkinsfile to read CodeRabbit review state.
Allow Jenkins to Run Docker
sudo usermod -aG docker jenkins
sudo systemctl restart jenkins
sudo -u jenkins docker psStore Credentials in Jenkins
Navigate to Manage Jenkins > Credentials > System > Global credentials and add:
| ID | Kind | Value |
|---|---|---|
dockerhub-credentials | Username with password | Docker Hub username + access token |
github-token | Secret text | GitHub personal access token with repo and pull_requests scopes |
kubeconfig-k3s | Secret file | Contents of /etc/rancher/k3s/k3s.yaml (with server IP corrected) |
Integrating AI Review Gates into the Jenkinsfile
The AI review gate works as follows:
- Jenkins detects a PR event via GitHub webhook.
- After tests pass, Jenkins calls the GitHub Reviews API to check whether CodeRabbit has posted a review with
CHANGES_REQUESTEDstate. - If CodeRabbit has requested changes, the pipeline fails the gate stage and posts a comment back to the PR explaining the block.
- If CodeRabbit approved or only left comments (no blocking review), the pipeline continues.
Create Jenkinsfile at the repository root:
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'docker.io'
DOCKER_IMAGE_NAME = 'yourdockerhubuser/myapp'
DOCKER_CREDENTIALS = 'dockerhub-credentials'
GITHUB_TOKEN_ID = 'github-token'
GITHUB_REPO = 'your-org/your-repo'
K8S_NAMESPACE = 'production'
K8S_DEPLOYMENT = 'myapp'
CODERABBIT_BOT = 'coderabbitai[bot]'
}
options {
disableConcurrentBuilds()
timeout(time: 30, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '10'))
}
stages {
stage('Checkout') {
steps {
checkout scm
script {
env.GIT_SHORT_SHA = sh(
script: 'git rev-parse --short HEAD',
returnStdout: true
).trim()
env.IMAGE_TAG = "${env.DOCKER_IMAGE_NAME}:${env.GIT_SHORT_SHA}"
env.IMAGE_TAG_LATEST = "${env.DOCKER_IMAGE_NAME}:latest"
// Capture PR number from the GitHub webhook environment variable
// Jenkins GitHub plugin sets CHANGE_ID for PR builds
env.PR_NUMBER = env.CHANGE_ID ?: ''
echo "PR number: ${env.PR_NUMBER}"
echo "Building image: ${env.IMAGE_TAG}"
}
}
}
stage('Install Dependencies') {
steps {
sh '''
node --version
npm --version
npm ci
'''
}
}
stage('Lint') {
steps {
sh 'npm run lint'
}
}
stage('Test') {
steps {
sh 'npm test -- --ci --coverage'
}
post {
always {
junit allowEmptyResults: true, testResults: 'test-results/*.xml'
}
}
}
stage('AI Review Gate') {
// Only enforce the gate on pull request builds, not direct pushes to main
when {
expression { return env.PR_NUMBER != '' }
}
steps {
script {
withCredentials([string(credentialsId: env.GITHUB_TOKEN_ID, variable: 'GH_TOKEN')]) {
// Poll CodeRabbit review state with retries
// CodeRabbit may take 1-3 minutes to post its review after the PR is opened
def maxAttempts = 12
def attempt = 0
def reviewState = 'PENDING'
while (attempt < maxAttempts && reviewState == 'PENDING') {
attempt++
echo "Polling GitHub Reviews API - attempt ${attempt}/${maxAttempts}"
def apiResponse = sh(
script: """
curl -s -H "Authorization: token ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${env.GITHUB_REPO}/pulls/${env.PR_NUMBER}/reviews"
""",
returnStdout: true
).trim()
// Extract the most recent review state from CodeRabbit bot
def codeRabbitState = sh(
script: """
echo '${apiResponse}' | jq -r '
[ .[] | select(.user.login == "${env.CODERABBIT_BOT}") ] |
last |
.state // "PENDING"
'
""",
returnStdout: true
).trim()
echo "CodeRabbit review state: ${codeRabbitState}"
if (codeRabbitState == 'CHANGES_REQUESTED') {
reviewState = 'BLOCKED'
} else if (codeRabbitState == 'APPROVED' || codeRabbitState == 'COMMENTED') {
reviewState = 'PASSED'
} else {
// PENDING or no review yet - wait and retry
if (attempt < maxAttempts) {
echo "No CodeRabbit review yet, waiting 30 seconds..."
sleep(30)
}
}
}
if (reviewState == 'BLOCKED') {
// Post a comment to the PR explaining the block
sh """
curl -s -X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${env.GITHUB_REPO}/issues/${env.PR_NUMBER}/comments" \
-d '{"body": ":robot: **Jenkins Pipeline Blocked**\\n\\nCodeRabbit has requested changes on this PR. The deployment pipeline will not proceed until all CodeRabbit issues are resolved and the review status changes to Approved or Commented.\\n\\nBuild: ${env.BUILD_URL}"}'
"""
error("AI Review Gate FAILED: CodeRabbit has requested changes on PR #${env.PR_NUMBER}. Resolve all flagged issues before this pipeline can proceed.")
} else if (reviewState == 'PENDING') {
// Timed out waiting for CodeRabbit - treat as non-blocking warning, not a hard fail
// Change to error() if you want to enforce a hard block on timeout
echo "WARNING: CodeRabbit review not received within timeout. Proceeding with caution."
} else {
echo "AI Review Gate PASSED: CodeRabbit review state is ${reviewState}"
}
}
}
}
}
stage('Build Docker Image') {
// Only build after the AI gate passes
steps {
sh """
docker build \
--label git-commit=${env.GIT_SHORT_SHA} \
--label build-date=\$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t ${env.IMAGE_TAG} \
-t ${env.IMAGE_TAG_LATEST} \
.
"""
}
}
stage('Push to Registry') {
steps {
withCredentials([usernamePassword(
credentialsId: env.DOCKER_CREDENTIALS,
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh """
echo "\$DOCKER_PASS" | docker login -u "\$DOCKER_USER" --password-stdin ${env.DOCKER_REGISTRY}
docker push ${env.IMAGE_TAG}
docker push ${env.IMAGE_TAG_LATEST}
"""
}
}
post {
always {
sh 'docker logout'
}
}
}
stage('Deploy to Kubernetes') {
// Only deploy on merges to main, not on PR builds
when {
branch 'main'
}
steps {
withCredentials([file(credentialsId: 'kubeconfig-k3s', variable: 'KUBECONFIG')]) {
sh """
kubectl set image deployment/${env.K8S_DEPLOYMENT} \
${env.K8S_DEPLOYMENT}=${env.IMAGE_TAG} \
-n ${env.K8S_NAMESPACE}
kubectl rollout status deployment/${env.K8S_DEPLOYMENT} \
-n ${env.K8S_NAMESPACE} \
--timeout=300s
"""
}
}
}
}
post {
success {
echo "Pipeline completed. Image: ${env.IMAGE_TAG}"
}
failure {
echo "Pipeline failed. Check stage logs above."
}
cleanup {
sh """
docker rmi ${env.IMAGE_TAG} || true
docker rmi ${env.IMAGE_TAG_LATEST} || true
docker image prune -f
"""
}
}
}Blocking Merges on AI Review Failures
The Jenkinsfile gate above fails the pipeline build, but you also need to prevent the PR from being merged on GitHub regardless of whether someone bypasses Jenkins. This is enforced at the GitHub branch protection level.
Configure Branch Protection on main
Go to your repository: Settings > Branches > Add rule
- Branch name pattern:
main - Enable: Require a pull request before merging
- Enable: Require approvals: 1 minimum
- Enable: Require status checks to pass before merging
- Search for and add:
continuous-integration/jenkins/pr-merge(Jenkins reports this status) - Search for and add:
CodeRabbit(CodeRabbit reports a check status)
- Search for and add:
- Enable: Require branches to be up to date before merging
- Enable: Do not allow bypassing the above settings (applies to admins too)
Click Save changes.
Now both Jenkins and CodeRabbit must report green before the Merge button is enabled on any PR.
8.2 Configure CodeRabbit to Report a Blocking Check
In .coderabbit.yaml, ensure the review mode is set to post a formal review (not just comments), which triggers a GitHub check status:
reviews:
request_changes_workflow: true
# This setting causes CodeRabbit to post a formal "Request Changes" review
# which GitHub reads as a blocking check when branch protection is enabledWith request_changes_workflow: true and branch protection requiring CodeRabbit’s check, a PR with unresolved AI issues literally cannot be merged (the button is grayed out).
9. Docker Build and Push Stage
The Dockerfile for a Node.js application:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src/ ./src/
FROM node:20-alpine AS production
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/src ./src
COPY --chown=appuser:appgroup package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "src/index.js"]Build locally to verify before committing:
docker build -t myapp:local .
docker run --rm -p 3000:3000 myapp:local
curl http://localhost:3000/healthDeploying to K3s
Install K3s
On the K3s node:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \
--disable traefik \
--tls-san <k3s-node-public-ip>" sh -
sudo systemctl status k3s
sudo kubectl get nodesDistribute kubeconfig to CI Server
On the K3s node:
sudo cat /etc/rancher/k3s/k3s.yamlOn the CI server:
sudo mkdir -p /var/lib/jenkins/.kube
# Paste the k3s.yaml content, then fix the server address:
sudo sed -i 's/127.0.0.1/<k3s-node-ip>/g' /var/lib/jenkins/.kube/config
sudo chown -R jenkins:jenkins /var/lib/jenkins/.kube
sudo chmod 600 /var/lib/jenkins/.kube/config
# Verify
sudo -u jenkins kubectl get nodesKubernetes Manifests
k8s/namespace.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: productionk8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
replicas: 2
selector:
matchLabels:
app: myapp
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: myapp
spec:
terminationGracePeriodSeconds: 30
containers:
- name: myapp
image: yourdockerhubuser/myapp:latest
ports:
- containerPort: 3000
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 15
periodSeconds: 20k8s/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: production
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 3000
type: ClusterIPApply the initial manifests:
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yamlUsing the GitHub API to Parse AI Review Signals
This section details the GitHub API calls used in the pipeline gate stage so you can extend or debug them.
List Reviews on a PR
export GH_TOKEN="your-github-token"
export REPO="your-org/your-repo"
export PR_NUMBER="42"
curl -s \
-H "Authorization: token $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
| jq '.'
A review object looks like this:
{
"id": 12345678,
"user": {
"login": "coderabbitai[bot]"
},
"body": "**Actionable comments posted: 2**...",
"state": "CHANGES_REQUESTED",
"submitted_at": "2024-11-01T10:23:45Z"
}Possible state values: APPROVED, CHANGES_REQUESTED, COMMENTED, DISMISSED, PENDING.
Extract Only the Latest CodeRabbit Review State
curl -s \
-H "Authorization: token $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
| jq -r '
[ .[] | select(.user.login == "coderabbitai[bot]") ] |
last |
.state // "PENDING"
'This filters to only CodeRabbit’s reviews, takes the most recent one, and returns its state. If no review has been posted yet, it returns PENDING.
Post a Comment Back to the PR
Used in the pipeline to explain a gate failure to the developer:
curl -s -X POST \
-H "Authorization: token $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments" \
-d '{
"body": ":robot: Pipeline blocked: CodeRabbit has requested changes. Resolve all flagged issues before retrying."
}'Check PR Review Comments Count (Optional Metric)
If you want to surface in build output how many issues CodeRabbit found:
curl -s \
-H "Authorization: token $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/comments" \
| jq '[.[] | select(.user.login == "coderabbitai[bot]")] | length'This lets you log or threshold on comment count: e.g., fail the gate if more than 5 inline issues were flagged even if the review state is COMMENTED rather than CHANGES_REQUESTED.
Secrets Management
GitHub Token Scopes
The GitHub personal access token (or GitHub App installation token) used by Jenkins for API calls needs these scopes:
repo(full repository access)pull_requests(read and write)
Create it at GitHub > Settings > Developer settings > Personal access tokens > Tokens (classic). Store it in Jenkins as a Secret text credential with ID github-token.
Kubernetes Secrets for the Application
kubectl create secret generic myapp-secrets \
--namespace production \
--from-literal=db-password='your-db-password-here'
kubectl get secret myapp-secrets -n productionReference in the Deployment spec:
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-secrets
key: db-password12.3 Never Log Tokens in Jenkins
The withCredentials block in the Jenkinsfile masks credential values in console output. Always wrap any shell command that uses a token inside withCredentials. Never use echo to print a token value, even for debugging. Jenkins masks it in output but it still constitutes a bad practice and may surface in third-party log shippers.
Full Pipeline Execution Walkthrough
Triggering the Pipeline on a PR
git checkout -b feature/new-endpoint
# Make changes
git add .
git commit -m "feat: add /users endpoint"
git push origin feature/new-endpointOpen a pull request on GitHub. This triggers:
- CodeRabbit starts analyzing the PR (asynchronously, 1-3 minutes).
- Jenkins pipeline starts via webhook (immediately).
- Jenkins runs Checkout, Install, Lint, Test stages.
- Jenkins enters the AI Review Gate stage and begins polling for CodeRabbit’s verdict.
- If CodeRabbit finds issues, the pipeline fails at the gate and posts a comment.
- Developer resolves the issues, pushes more commits to the branch.
- New push re-triggers the pipeline. CodeRabbit re-reviews the updated diff.
- Once CodeRabbit approves (or posts only comments), the gate passes.
- Docker image is built and pushed with the git SHA tag.
- On merge to
main, the Deploy stage fires and updates the Kubernetes Deployment.
Monitoring the Rollout
# Watch pods update in real time
kubectl rollout status deployment/myapp -n production -w
# Confirm the new image SHA is running
kubectl get pods -n production \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'Rolling Back
kubectl rollout undo deployment/myapp -n production
kubectl rollout status deployment/myapp -n productionTroubleshooting Reference
CodeRabbit not posting a review
# Verify the GitHub App is installed on the repository
# Go to: github.com/settings/installations > coderabbitai
# Check if .coderabbit.yaml has YAML syntax errors
cat .coderabbit.yaml | python3 -c "import sys, yaml; yaml.safe_load(sys.stdin)" && echo "YAML valid"
# Confirm the PR is not a draft (CodeRabbit skips draft PRs by default)
# Convert draft to Ready for Review on GitHubJenkins AI Review Gate times out waiting for CodeRabbit
The gate polls for up to 6 minutes (12 attempts x 30 seconds). If CodeRabbit consistently takes longer, increase maxAttempts or sleep() in the gate stage. If CodeRabbit is down or rate-limited, the gate currently falls through with a warning. Change this to error() if you want a hard fail on timeout:
} else if (reviewState == 'PENDING') {
error("AI Review Gate TIMED OUT: No CodeRabbit review received within 6 minutes.")
}GitHub API returning 401
# Test the token manually
curl -s -H "Authorization: token <your-token>" https://api.github.com/user | jq '.login'
# Should return your GitHub username
# Verify the token is stored correctly in Jenkins
# Manage Jenkins > Credentials > verify ID matches GITHUB_TOKEN_ID in JenkinsfileGitHub API returning 403 on PR comments
The token lacks pull_requests write scope. Regenerate the token with the correct scopes and update the Jenkins credential.
jq parse error in gate stage
The API response may contain characters that break shell quoting when interpolated into the jq command. Write the response to a temp file instead:
sh """
curl -s -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${env.GITHUB_REPO}/pulls/${env.PR_NUMBER}/reviews" \
> /tmp/reviews.json
jq -r '
[ .[] | select(.user.login == "${env.CODERABBIT_BOT}") ] |
last | .state // "PENDING"
' /tmp/reviews.json
"""Pipeline deploys on PR builds instead of only on main
The when { branch 'main' } condition in the Deploy stage relies on the Jenkins GitHub plugin correctly setting the BRANCH_NAME environment variable. For PR builds, BRANCH_NAME is set to PR-<number>. Verify:
stage('Debug Branch') {
steps {
echo "BRANCH_NAME: ${env.BRANCH_NAME}"
echo "CHANGE_ID: ${env.CHANGE_ID}"
}
}If BRANCH_NAME is not being set, ensure the pipeline job type is Multibranch Pipeline, not a standard Pipeline job. Multibranch Pipeline is required for the Jenkins GitHub plugin to correctly populate PR-specific environment variables.
This pipeline enforces a concrete AI quality gate that cannot be bypassed: CodeRabbit must sign off on every pull request before code reaches a Docker image or a production cluster. The combination of GitHub branch protection rules and the Jenkins gate stage creates two independent enforcement points, meaning a developer cannot merge by bypassing Jenkins alone.