Skip to main content

Jenkins Pipeline — Declarative, Scripted, and Blue Ocean Explained

· 6 min read
Goel Academy
DevOps & Cloud Learning Hub

Jenkins has been around since 2011 (as Hudson since 2004) and it runs over 50% of CI/CD pipelines worldwide. Some people say it is old and clunky. They are partially right. But Jenkins is also incredibly powerful, endlessly extensible, and — unlike SaaS alternatives — you own every piece of it.

Jenkins Architecture: Controller and Agents

Before writing a single pipeline, understand how Jenkins works under the hood.

The Controller (formerly "Master") is the brain. It manages configurations, schedules builds, serves the web UI, and distributes work. Agents (formerly "Slaves") are the machines that actually run your builds. You can have dozens of agents with different operating systems, tools, and capabilities.

┌──────────────┐     ┌──────────────┐
│ Controller │────>│ Agent: Linux │ (builds, tests)
│ (brain) │ └──────────────┘
│ │ ┌──────────────┐
│ Web UI │────>│ Agent: Docker│ (container builds)
│ Scheduler │ └──────────────┘
│ Config │ ┌──────────────┐
│ │────>│ Agent: macOS │ (iOS builds)
└──────────────┘ └──────────────┘

Rule of thumb: Never run builds on the controller. It should only schedule and delegate.

Declarative Pipeline: The Modern Standard

Declarative Pipeline is Jenkins' recommended syntax. It is structured, opinionated, and catches mistakes early with built-in validation.

// Jenkinsfile (Declarative Pipeline)
pipeline {
agent any

environment {
APP_NAME = 'my-web-app'
DEPLOY_ENV = 'staging'
REGISTRY = credentials('docker-registry-creds')
}

options {
timeout(time: 30, unit: 'MINUTES')
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '10'))
}

stages {
stage('Checkout') {
steps {
checkout scm
}
}

stage('Install Dependencies') {
steps {
sh 'npm ci'
}
}

stage('Lint & Test') {
parallel {
stage('Lint') {
steps {
sh 'npm run lint'
}
}
stage('Unit Tests') {
steps {
sh 'npm test -- --coverage'
}
}
stage('Security Scan') {
steps {
sh 'npm audit --audit-level=high'
}
}
}
}

stage('Build') {
steps {
sh 'npm run build'
archiveArtifacts artifacts: 'dist/**', fingerprint: true
}
}

stage('Deploy to Staging') {
when {
branch 'main'
}
steps {
sh './scripts/deploy.sh staging'
}
}
}

post {
always {
junit '**/test-results/*.xml'
cleanWs()
}
success {
slackSend channel: '#deployments',
message: "✅ ${APP_NAME} build #${BUILD_NUMBER} succeeded"
}
failure {
slackSend channel: '#deployments',
message: "❌ ${APP_NAME} build #${BUILD_NUMBER} failed"
}
}
}

Key Declarative Sections

  • pipeline {} — The root. Everything goes inside this block.
  • agent — Where the pipeline runs (any, none, label, or docker).
  • environment — Environment variables available to all stages.
  • stages — The ordered list of stages to execute.
  • post — Actions that run after all stages (always, success, failure, unstable, changed).

Scripted Pipeline: Full Groovy Power

Scripted Pipeline gives you the full power of Groovy. It is more flexible but harder to read and debug.

// Jenkinsfile (Scripted Pipeline)
node('linux') {
def appName = 'my-web-app'

try {
stage('Checkout') {
checkout scm
}

stage('Build') {
sh 'npm ci'
sh 'npm run build'
}

stage('Test') {
sh 'npm test'

// Groovy logic — not possible in Declarative
def testResults = readFile('test-report.json')
def parsed = readJSON text: testResults
if (parsed.failures > 0) {
error "Found ${parsed.failures} test failures"
}
}

stage('Deploy') {
if (env.BRANCH_NAME == 'main') {
withCredentials([
usernamePassword(
credentialsId: 'deploy-creds',
usernameVariable: 'USER',
passwordVariable: 'PASS'
)
]) {
sh "./deploy.sh --user ${USER} --pass ${PASS}"
}
} else {
echo "Skipping deploy for branch: ${env.BRANCH_NAME}"
}
}

currentBuild.result = 'SUCCESS'
} catch (Exception e) {
currentBuild.result = 'FAILURE'
throw e
} finally {
cleanWs()
emailext subject: "${appName} - Build ${currentBuild.result}",
body: "Build #${BUILD_NUMBER} ${currentBuild.result}",
recipientProviders: [culprits()]
}
}

When to use Scripted over Declarative: When you need complex conditional logic, loops over dynamic data, custom Groovy functions, or runtime-generated stages.

Credentials Management

Jenkins handles credentials securely through its built-in credential store. Never hardcode secrets.

pipeline {
agent any

stages {
stage('Deploy') {
steps {
// Username/Password credentials
withCredentials([
usernamePassword(
credentialsId: 'docker-hub',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)
]) {
sh 'echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin'
sh 'docker push myapp:latest'
}

// SSH key credentials
withCredentials([
sshUserPrivateKey(
credentialsId: 'deploy-ssh-key',
keyFileVariable: 'SSH_KEY'
)
]) {
sh 'ssh -i "$SSH_KEY" deploy@server "restart-app"'
}

// Secret text
withCredentials([
string(credentialsId: 'slack-webhook', variable: 'WEBHOOK')
]) {
sh 'curl -X POST -d "Build done" $WEBHOOK'
}
}
}
}
}

Docker Agent: Disposable Build Environments

One of Jenkins' best features is running each stage inside a Docker container. No more "but it works on the build server."

pipeline {
agent none // No global agent

stages {
stage('Build Frontend') {
agent {
docker {
image 'node:20-alpine'
args '-v $HOME/.npm:/root/.npm' // Cache npm
}
}
steps {
sh 'npm ci && npm run build'
stash includes: 'dist/**', name: 'frontend-build'
}
}

stage('Build Backend') {
agent {
docker { image 'golang:1.22' }
}
steps {
sh 'go build -o server ./cmd/server'
stash includes: 'server', name: 'backend-build'
}
}

stage('Integration Tests') {
agent {
docker {
image 'docker/compose:latest'
args '-v /var/run/docker.sock:/var/run/docker.sock'
}
}
steps {
unstash 'frontend-build'
unstash 'backend-build'
sh 'docker compose -f docker-compose.test.yml up --abort-on-container-exit'
}
}
}
}

Webhook Triggers

Set up automatic builds when code is pushed.

pipeline {
agent any

triggers {
// Poll SCM every 5 minutes (not recommended — use webhooks)
// pollSCM('H/5 * * * *')

// GitHub webhook trigger (preferred)
githubPush()

// Cron schedule
cron('H 2 * * 1-5') // Weekdays at ~2 AM

// Trigger on upstream job completion
upstream(
upstreamProjects: 'build-base-image',
threshold: hudson.model.Result.SUCCESS
)
}

stages {
stage('Build') {
steps {
sh 'make build'
}
}
}
}

For GitHub webhooks, configure your repo: Settings > Webhooks > Add webhook with URL https://your-jenkins.com/github-webhook/.

Jenkins vs GitHub Actions

FeatureJenkinsGitHub Actions
HostingSelf-hosted (you manage)Cloud-hosted (GitHub manages)
CostFree (OSS) + infra costFree tier + per-minute billing
ConfigJenkinsfile (Groovy)YAML workflows
Plugins1,800+ plugins20,000+ marketplace actions
RunnersYour agents (full control)GitHub-hosted or self-hosted
SecretsCredential storeRepository/Org secrets
Parallelparallel {} blockmatrix strategy
DockerNative Docker agentContainer actions
UIClassic + Blue OceanBuilt-in (clean, modern)
Learning CurveSteep (Groovy + admin)Gentle (YAML, no infra)
Best ForComplex enterprises, air-gappedGitHub-centric teams, OSS
MaintenanceYou patch, update, backupZero maintenance

When to choose Jenkins: You need air-gapped environments, complex enterprise integrations, or full control over infrastructure. Also when you are not on GitHub.

When to choose GitHub Actions: Your code is already on GitHub, you want zero infrastructure, or your team is small-to-medium and values simplicity.

Blue Ocean UI

Jenkins Classic UI looks like it was designed in 2005 — because it was. Blue Ocean is the modern alternative.

Blue Ocean provides a visual pipeline editor, clear stage visualization, and a modern log viewer. Install it from Manage Jenkins > Plugins > Available and search for "Blue Ocean."

# Access Blue Ocean
# Navigate to: http://your-jenkins/blue

# Blue Ocean features:
# - Visual pipeline editor (create pipelines without writing Groovy)
# - Branch and PR visualization
# - Per-stage logs (click any stage to see its output)
# - GitHub/Bitbucket integration wizard

Essential Plugins

These plugins turn Jenkins from basic to powerful:

Pipeline              - Pipeline as Code support (installed by default now)
Docker Pipeline - Docker agent support
Blue Ocean - Modern UI
Credentials Binding - Inject secrets into builds
Git - Git SCM integration
Slack Notification - Post build results to Slack
JUnit - Test result visualization
Pipeline Utility Steps - readJSON, readYaml, zip/unzip helpers
Timestamper - Add timestamps to console output
Workspace Cleanup - Clean workspace after builds

Jenkins is not going anywhere. It is battle-tested, infinitely customizable, and runs behind corporate firewalls where SaaS cannot reach. Learn it well — you will encounter it in nearly every enterprise.


Next in our DevOps series, we will tackle the file format that powers all of this — YAML — with a complete guide that will stop you from Googling YAML syntax forever.