Dev-QA-Stage-Prod-Pipelines


Dev → QA → Stage → Prod → DR
using a single artifact repository (ECR/JFrog/Nexus) with tagging at each stage, and no rebuilding after Dev.
Flow Overview
Branches:
- dev → Development deployments
- qa → QA deployments
- release/* → Stage & Prod deployments
- main → Production
Artifact/Image Tags:
- dev-<build_number> → built from dev branch
- qa-<build_number> → promoted from dev
- stage-<build_number> → promoted from qa
- prod-vX.Y.Z → promoted from stage
1️⃣ Dev Pipeline (Build & Unit Tests)
Here’s a Dev pipeline with:
Linting
GitLeaks scan
SonarQube Quality Gate
Trivy Docker image scan
Deployment to Dev namespace in Kubernetes
Slack notifications:
On failure → to committer and
#devchannel
On success → to
#devchannel
(build & deploy complete) and#qachannel
(ready for testing)
Trigger: Code push to dev
branch
Goal: Build image → run unit tests → push dev
tag to registry → deploy to Dev namespace.
pipeline {
agent any
environment {
SONARQUBE_SERVER = 'SonarQubeServer'
DOCKER_IMAGE = "myapp:${env.BUILD_NUMBER}"
K8S_NAMESPACE = "dev"
SLACK_DEV_CHANNEL = "#devchannel"
SLACK_QA_CHANNEL = "#qachannel"
COMMITTER = sh(script: "git log -1 --pretty=format:'%ae'", returnStdout: true).trim()
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Linting') {
steps {
sh 'eslint . || true' // Or any language-specific linter
}
}
stage('Secrets Scan (GitLeaks)') {
steps {
sh 'gitleaks detect --source . --no-git --redact'
}
}
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv("${SONARQUBE_SERVER}") {
sh 'sonar-scanner'
}
}
}
stage('SonarQube Quality Gate') {
steps {
script {
timeout(time: 5, unit: 'MINUTES') {
def qg = waitForQualityGate()
if (qg.status != 'OK') {
sendSlack("${SLACK_DEV_CHANNEL}", "🚫 SonarQube Quality Gate FAILED for build ${env.BUILD_NUMBER}")
sendSlackUser("${COMMITTER}", "🚫 Your commit failed SonarQube Quality Gate in build ${env.BUILD_NUMBER}")
error "Pipeline aborted due to Quality Gate failure: ${qg.status}"
}
}
}
}
}
stage('Build Docker Image') {
steps {
sh "docker build -t ${DOCKER_IMAGE} ."
}
}
stage('Trivy Scan Docker Image') {
steps {
sh "trivy image --exit-code 1 --severity HIGH,CRITICAL ${DOCKER_IMAGE}"
}
}
stage('Push Image to Registry') {
steps {
sh "docker push ${DOCKER_IMAGE}"
}
}
stage('Deploy to Dev Namespace (K8s)') {
steps {
sh "kubectl set image deployment/myapp myapp=${DOCKER_IMAGE} -n ${K8S_NAMESPACE}"
}
}
}
post {
success {
script {
sendSlack("${SLACK_DEV_CHANNEL}", "✅ Build & Deploy successful for build ${env.BUILD_NUMBER}")
sendSlack("${SLACK_QA_CHANNEL}", "🧪 Dev build ${env.BUILD_NUMBER} is ready for QA testing.")
}
}
failure {
script {
sendSlack("${SLACK_DEV_CHANNEL}", "❌ Build FAILED for build ${env.BUILD_NUMBER}")
sendSlackUser("${COMMITTER}", "❌ Your commit caused build ${env.BUILD_NUMBER} to fail. Please check Jenkins.")
}
}
}
}
def sendSlack(channel, message) {
slackSend(channel: channel, color: '#FF0000', message: message)
}
def sendSlackUser(email, message) {
slackSend(channel: "@${email.split('@')[0]}", color: '#FF0000', message: message)
}
🔹 How It Works
Checkout code from
dev
branch.Linting → catches syntax/style issues.
GitLeaks → detects secrets in code.
SonarQube → runs code analysis + waits for Quality Gate pass.
Docker build → builds image with build number tag.
Trivy scan → blocks build on HIGH/CRITICAL vulnerabilities.
Push image → to single shared registry (no per-env repos).
Deploy to Dev namespace in Kubernetes.
Slack Notifications →
Failure: committer + dev channel.
Success: dev channel + QA channel.
2️⃣ QA Pipeline (Promote & Regression Tests)
Trigger: Merge dev
→ qa
Goal: Pull Dev image → retag as QA → push → deploy to QA namespace.
pipeline {
agent any
environment {
IMAGE_REPO = "123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp"
QA_TAG = "qa-${BUILD_NUMBER}"
DEV_TAG = "dev-${params.DEV_BUILD_NUMBER}"
}
parameters {
string(name: 'DEV_BUILD_NUMBER', description: 'Build number from Dev pipeline')
}
stages {
stage('Promote Image') {
steps {
sh """
docker pull ${IMAGE_REPO}:${DEV_TAG}
docker tag ${IMAGE_REPO}:${DEV_TAG} ${IMAGE_REPO}:${QA_TAG}
docker push ${IMAGE_REPO}:${QA_TAG}
"""
}
}
stage('Deploy to QA') {
steps {
sh "kubectl set image deployment/myapp myapp=${IMAGE_REPO}:${QA_TAG} -n qa"
}
}
stage('Regression Tests') {
steps {
sh "pytest tests/regression"
}
}
}
}
3️⃣ Stage Pipeline (Pre-Prod)
Trigger: Merge qa
→ release/x.y.z
Goal: Promote QA image → Stage → deploy → run load tests.
pipeline {
agent any
environment {
IMAGE_REPO = "123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp"
STAGE_TAG = "stage-${BUILD_NUMBER}"
QA_TAG = "qa-${params.QA_BUILD_NUMBER}"
}
parameters {
string(name: 'QA_BUILD_NUMBER', description: 'Build number from QA pipeline')
}
stages {
stage('Promote Image') {
steps {
sh """
docker pull ${IMAGE_REPO}:${QA_TAG}
docker tag ${IMAGE_REPO}:${QA_TAG} ${IMAGE_REPO}:${STAGE_TAG}
docker push ${IMAGE_REPO}:${STAGE_TAG}
"""
}
}
stage('Deploy to Stage') {
steps {
sh "kubectl set image deployment/myapp myapp=${IMAGE_REPO}:${STAGE_TAG} -n stage"
}
}
stage('Load Tests') {
steps {
sh "locust -f load_tests.py"
}
}
}
}
4️⃣ Prod Pipeline
Q. So in prod do we run prod pipeline manually or by tag ?
In most organizations, Prod pipelines are triggered by tag creation, not manual branch pushes.
Flow:
Stage is fully tested.
Release branch is merged into main (or master).
A Git tag is created on main (e.g., v1.2.3).
Jenkins detects the tag → triggers the Prod pipeline automatically.
Pipeline pauses at manual approval before deployment.
After approval → deploys the already built Stage image to Prod.
✅ Manual triggering is generally avoided for Prod to maintain traceability and consistency.
- The only manual step is the approval inside the pipeline, not starting it.
You want me to modify your Prod Jenkinsfile to auto-capture the Git tag instead of passing it manually?
Prod Jenkinsfile updated with Slack notifications for:
Approval request
Deployment success
Deployment failure
Trigger: Merge release/x.y.z
→ main
+ tag (e.g., v1.2.3
)
Goal: Promote Stage image → Prod → smoke tests.
pipeline {
agent any
environment {
IMAGE_REPO = "123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp"
K8S_NAMESPACE = "prod"
SLACK_DEV_CHANNEL = "#devchannel"
SLACK_RELEASE_CHANNEL = "#release-channel"
}
stages {
stage('Set Variables') {
steps {
script {
// Capture Git tag that triggered the build
PROD_TAG = env.GIT_TAG_NAME ?: error("No Git tag found. Trigger the pipeline by creating a tag on main.")
STAGE_IMAGE = "${IMAGE_REPO}:${PROD_TAG}"
echo "Using Stage image: ${STAGE_IMAGE}"
}
}
}
stage('Approval') {
steps {
script {
slackSend(channel: "${SLACK_DEV_CHANNEL}", color: "#FFA500",
message: "⚠️ Approval required for deploying ${PROD_TAG} to Production. Please review and approve.")
input message: "Do you approve deploying ${PROD_TAG} to Production?", ok: "Deploy"
}
}
}
stage('Deploy to Prod') {
steps {
sh """
docker pull ${STAGE_IMAGE}
kubectl set image deployment/myapp myapp=${STAGE_IMAGE} -n ${K8S_NAMESPACE}
"""
}
}
stage('Smoke Tests') {
steps {
sh "pytest tests/smoke"
}
}
}
post {
success {
script {
slackSend(channel: "${SLACK_DEV_CHANNEL}", color: "#36A64F",
message: "✅ Prod deployment SUCCESS for ${PROD_TAG}")
slackSend(channel: "${SLACK_RELEASE_CHANNEL}", color: "#36A64F",
message: "🚀 ${PROD_TAG} deployed to Production successfully.")
}
}
failure {
script {
slackSend(channel: "${SLACK_DEV_CHANNEL}", color: "#FF0000",
message: "❌ Prod deployment FAILED for ${PROD_TAG}")
slackSend(channel: "${SLACK_RELEASE_CHANNEL}", color: "#FF0000",
message: "⚠️ Deployment FAILED for ${PROD_TAG}. Check Jenkins logs.")
}
}
}
}
✅ Flow
Jenkins triggers automatically on Git tag push to main.
Slack notification is sent to #devchannel asking for approval.
Pipeline pauses for manual approval.
Once approved:
Stage/release image is pulled and deployed to Prod.
Smoke tests run.
Slack notifications are sent to #devchannel and #release-channel for success/failure.
This keeps Prod deployment safe, traceable, and fully communicated.
🔹 How it works
Jenkins job is configured to build when a Git tag is pushed (refspec:
+refs/tags/*:refs/remotes/origin/tags/*
).Pipeline automatically captures the tag via
env.GIT_TAG_NAME
.Manual approval stage ensures safe deployment.
Deploys the Stage image with the same tag to Prod namespace.
Runs post-deployment smoke tests.
This way, there’s no manual parameter entry, and the pipeline is fully Git tag-driven.
Q. So for above prod Jenkinsfile we have manual approval for deployment to production right ?
Yes ✅ — in most setups, the Prod Jenkins pipeline includes a manual approval step before deploying, even if it’s triggered by a tag.
Why:
Safety: prevent accidental deployment of a release tag.
Ensures DevOps or Release Manager verifies everything (Stage tests, release notes, etc.) before Prod.
How it fits:
Tag is pushed on main → Jenkins pipeline triggers.
Pipeline pauses at “Input/Approval” stage.
DevOps/Release Manager reviews details → clicks “Proceed” in Jenkins.
Pipeline continues → deploys the already tested release image to Prod.
If you want, I can modify the Prod Jenkinsfile to include a proper manual approval/input step before deployment.
Q. Why we deploy the image even though we already have it
“Deployment” ≠ build — the image exists in the registry, but Kubernetes doesn’t know about it until you update your deployment.
kubectl set image tells K8s:
- “Use this specific image in this deployment/pod.”
Without deployment, the image is just sitting in your registry — no containers are running in Prod.
Deployment ensures:
Pods are updated to the new image.
Rolling update or restart happens safely.
Prod environment actually reflects the release.
So even though the artifact/image exists, deployment is the step that makes it live in the target environment.
Q. So prod triggers when tag is created on main branch right ?:
Yes — in most CI/CD setups, Prod deployment is triggered when a version tag is created on the main (or master) branch.
Typical flow:
Release branch is fully tested in Stage.
Release branch is merged into main.
Version tag is created (e.g., v1.3.0).
CI/CD pipeline detects this tag push → pulls the already tested Stage/Release image from the registry → deploys to Production.
The key point is: we don’t rebuild the image for Prod — we promote the tested image.
Do you want me to also add the Prod pipeline YAML/Jenkinsfile matching this tagging trigger?
Q. And how dev/qa/stage pipelines runs by tag or push ?
In short —
Dev pipeline → triggered by push (or PR merge) to dev branch.
QA pipeline → triggered by merge to qa branch (or sometimes by promoting a Dev image tag).
Stage pipeline → triggered by merge to release branch (or promoting a QA image tag).
Prod pipeline → triggered by tag creation on main branch.
So:
Push → Dev
Merge → QA
Merge → Stage
Tag → Prod
5️⃣ DR Pipeline
Trigger: Auto after Prod deployment
Goal: Deploy same prod image to DR.
pipeline {
agent any
environment {
IMAGE_REPO = "123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp"
PROD_TAG = "v${params.VERSION}"
}
parameters {
string(name: 'VERSION', description: 'Prod version to deploy')
}
stages {
stage('Deploy to DR') {
steps {
sh "kubectl set image deployment/myapp myapp=${IMAGE_REPO}:${PROD_TAG} -n dr"
}
}
stage('Smoke Tests') {
steps {
sh "pytest tests/smoke"
}
}
}
}
✅ Key Points
One artifact repository (ECR/JFrog/Nexus) with different tags per stage.
No rebuild after Dev → just retag & push.
Every environment has its own namespace in Kubernetes (
dev
,qa
,stage
,prod
,dr
).Merges control promotion, not code changes per environment.
Main branch always reflects Prod → after release is merged, merge main → dev to keep sync.
Q. So prod triggers when tag is created on main branch right ?
Yes — in most CI/CD setups, Prod deployment is triggered when a version tag is created on the main (or master) branch.
Typical flow:
Release branch is fully tested in Stage.
Release branch is merged into main.
Version tag is created (e.g., v1.3.0).
CI/CD pipeline detects this tag push → pulls the already tested Stage/Release image from the registry → deploys to Production.
The key point is: we don’t rebuild the image for Prod — we promote the tested image.
Q. How dev/qa/stage pipelines runs by tag or push in short
In short —
Dev pipeline → triggered by push (or PR merge) to dev branch.
QA pipeline → triggered by merge to qa branch (or sometimes by promoting a Dev image tag).
Stage pipeline → triggered by merge to release branch (or promoting a QA image tag).
Prod pipeline → triggered by tag creation on main branch.
So:
Push → Dev
Merge → QA
Merge → Stage
Tag → Prod
Subscribe to my newsletter
Read articles from Aditya Patil directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Aditya Patil
Aditya Patil
Hi, I'm Aditya — a Cloud & DevOps Engineer passionate about automating everything from CI/CD pipelines to multi-cloud infrastructure. I specialize in AWS, Kubernetes, Terraform, and GitOps tools like Argo CD. I’ve helped teams scale applications, cut cloud costs by 90%, and build disaster-ready infra. I love sharing real-world DevOps lessons, cloud cost optimization tips, and infrastructure design patterns. Let’s connect and simplify the cloud — one YAML file at a time ☁️⚙️