devops6 min read

Jenkins Tutorial: Learn CI/CD from Scratch (2026)

Jenkins Tutorial: Learn CI/CD from Scratch (2026)

Published:  |  Category: Devops  |  Reading time: ~15 min
Jenkins Tutorial: Learn CI/CD from Scratch (2026)

My first CI server was a Jenkins instance running on a repurposed desktop under someone's desk. It was unreliable, plugins were out of date, and builds failed for mysterious reasons. Yet it taught me the most important lesson of continuous integration: the feedback loop. When every commit is built and tested automatically, you catch integration issues in minutes instead of days. Jenkins has evolved significantly since those early days. With Declarative Pipelines, shared libraries, and native Docker and Kubernetes support, Jenkins in 2026 is a robust automation engine that orchestrates far more than just builds — it runs entire release pipelines, infrastructure deployments, and compliance checks.

Installing and Configuring Jenkins

Jenkins offers several installation paths: the native WAR file, OS packages, Docker images, or a Helm chart for Kubernetes. For new projects in 2026, I recommend the official Docker image or the Helm chart, as they simplify upgrades and plugin management. On first startup, Jenkins generates an initial admin password printed in the logs or stored in a file. The setup wizard guides you through installing suggested plugins — skip the suggestions and install only what you need to keep the instance lean. I typically start with Pipeline, Git, Blue Ocean, and Credentials Binding. Configure Jenkins through Manage Jenkins — set the Jenkins URL, configure system properties for executors, and set up security realms. I always use Matrix-based security or the Role-based Strategy plugin for team environments.

docker run -d --name jenkins -p 8080:8080 -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts-jdk17
docker logs jenkins # find initial password

Freestyle Jobs vs Declarative Pipelines

Freestyle jobs are Jenkins's original job type — you configure build steps, post-build actions, and triggers through the web UI. They are easy for simple builds but become unmanageable as complexity grows. Declarative Pipelines, introduced in 2015, define the entire build process as code in a Jenkinsfile checked into your repository. This is the approach I use exclusively now. Pipelines are version-controlled, testable, and self-documenting. A Declarative Pipeline has a structured syntax with agent, stages, steps, and post sections. The advantages over freestyle: you can add conditions, parallel execution, input gates for approvals, and automatic retries — all in code that lives alongside your application.

pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                checkout scmGit(branches: [[name: '*/main']], userRemoteConfigs: [[url: 'https://github.com/example/app.git']])
            }
        }
        stage('Build') {
            steps {
                sh 'npm install && npm run build'
            }
        }
        stage('Test') {
            steps {
                sh 'npm test'
            }
        }
    }
    post {
        failure {
            emailext subject: 'Build failed', to: 'team@example.com'
        }
    }
}

Pipeline Syntax, Stages, and Parallelism

The Declarative Pipeline syntax covers agent selection, environment variables, tools, parameters, triggers, and post-build conditions. The agent directive specifies where the pipeline runs — on any available executor, on a specific label, on Docker, or on Kubernetes. The environment block sets variables scoped to the pipeline or a stage. The tools block configures JDK, Maven, or Node versions from Jenkins global tool configuration. For large projects, parallel execution saves significant time. I parallelize independent stages like linting, unit tests, and integration tests across multiple executors. The failFast flag makes the entire build fail if any parallel branch fails. The stage graph in Blue Ocean visualizes these parallel branches clearly.

pipeline {
    agent none
    stages {
        stage('Quality') {
            parallel {
                stage('Lint') {
                    agent { label 'linux' }
                    steps { sh 'npm run lint' }
                }
                stage('Unit') {
                    agent { label 'linux' }
                    steps { sh 'npm run test:unit' }
                }
                stage('Integration') {
                    agent { label 'linux' }
                    steps { sh 'npm run test:integration' }
                }
            }
        }
    }
}

Credentials Management and Secret Handling

Every pipeline needs secrets — API keys, SSH keys, cloud credentials. Storing them in Jenkinsfile plaintext is a security violation. Jenkins Credentials Binding plugin provides encrypted storage with scoped access. I add credentials through Manage Credentials under the global or folder scope. Supported types include Username with password, SSH key, secret text, and certificate. In the pipeline, use the withCredentials step to inject credentials into environment variables. The bindings are masked in logs automatically. For cloud-native environments, I integrate Jenkins with HashiCorp Vault using the HashiCorp Vault Plugin, which fetches dynamic secrets at build time. This eliminates the need to rotate static credentials.

pipeline {
    environment {
        DOCKER_REGISTRY = credentials('docker-hub-creds')
    }
    stages {
        stage('Deploy') {
            steps {
                withCredentials([sshUserPrivateKey(keyFileVariable: 'SSH_KEY', credentialsId: 'prod-ssh')]) {
                    sh 'scp -i $SSH_KEY build.zip deploy@host:/opt/app'
                }
            }
        }
    }
}

Shared Libraries for Pipeline Reusability

When you have dozens of microservices, duplicating the same pipeline logic across repositories becomes a maintenance burden. Shared libraries let you define reusable pipeline code in a separate Git repository and load it in any Jenkinsfile. The library defines global functions, variables, and steps using Groovy. I organize shared libraries into src for utility classes and vars for pipeline DSL extensions. Each var file defines a call method that becomes a step in the pipeline. For example, a dockerBuild.groovy file encapsulates the entire image build and push logic with parameters for the registry, tag, and Dockerfile path. Declarative Pipelines load shared libraries with the @Library annotation at the top of the Jenkinsfile.

@Library('devops-toolkit@main') _
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                dockerBuild(repo: 'myapp', registry: 'ghcr.io', tag: version)
            }
        }
        stage('Deploy') {
            steps {
                kubernetesDeploy(manifest: 'k8s/deployment.yaml', namespace: 'prod')
            }
        }
    }
}

Distributed Builds with Jenkins Agents

Running everything on the master node is a bottleneck and a security risk. Jenkins agents (formerly called slaves) distribute build workloads across multiple machines. An agent connects to the master via SSH, JNLP, or WebSocket. I configure agent pools with labels — linux-arm, windows, docker, gpu — and set pipeline agent directives to match labels. The Kubernetes plugin takes this further: it dynamically provisions Pods as agents for each build. Each Pod can contain a JNLP container for the agent and sidecar containers for tools like Docker, Node.js, or Maven. This gives every build a clean, isolated environment and eliminates the snowflake-agent problem where flaky tests only fail on one machine.

pipeline {
    agent {
        kubernetes {
            yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: jnlp
    image: jenkins/inbound-agent:latest-jdk17
  - name: docker
    image: docker:27-cli
    command: ["cat"]
    tty: true
'''
        }
    }
    stages {
        stage('Build in container') {
            steps {
                container('docker') {
                    sh 'docker build -t myapp .'
                }
            }
        }
    }
}

Frequently Asked Questions

Should I use Declarative or Scripted Pipelines?

Declarative Pipelines are easier to read, structured, and have built-in validation. Scripted Pipelines are more flexible and let you use arbitrary Groovy code. I recommend Declarative for 95 percent of projects. Use Scripted only when you need complex programmatic logic that Declarative syntax cannot express.

How do I handle long-running pipelines that take hours?

Jenkins pipelines can resume across master restarts with checkpointing if you use the Pipeline Checkpoint plugin on supported storage. For extremely long pipelines, consider breaking them into separate pipelines or using asynchronous triggers. Ensure your agent is configured with appropriate timeout settings to avoid premature termination.

How many plugins should I install?

Install only what you need. Each plugin adds maintenance surface, potential security vulnerabilities, and UI clutter. A typical Jenkins instance needs 20 to 40 plugins. Audit your plugin list quarterly and remove unused ones. Plugin compatibility issues are a common cause of Jenkins instability.

What is the best way to back up Jenkins?

Back up the JENKINS_HOME directory, which contains all configuration, job definitions, and build records. The ThinBackup plugin handles scheduled backups. For cloud deployments, store JENKINS_HOME on persistent storage with snapshots. Also export your pipeline code — since Jenkinsfiles are in Git, your pipeline logic is already versioned.

Originally published on Ayodhyyya. Last updated June 1, 2026.