Jenkins Is Old. So What?
- Jun 18
- 6 min read
Every time I mention Jenkins at work, someone rolls their eyes. "Why are you still using that? Just use GitHub Actions."

I get it. Jenkins isn't flashy. It doesn't have a slick onboarding experience or a marketing team putting out blog posts about its latest AI integration. But before you write it off, let me push back a little — because I think the "Jenkins is dead" narrative says more about our industry's obsession with novelty than it does about the tool itself.
The Objections (And Why They Don't Hold Up)
"But GitHub Actions has everything out of the box!"
Sure — until it doesn't. Managed CI/CD platforms are excellent right up until the moment your workflow needs something truly custom. Then you're writing workarounds, hunting for community actions of questionable quality, or hitting hard limits on what the platform will let you do. Jenkins doesn't have that ceiling. At its core, it's a script executor with an orchestration layer on top. If you can write it, Jenkins will run it. That kind of flexibility is hard to replicate.
"But modern tools are way simpler to set up!"
True. But simple almost always means opinionated. You're trading control for convenience, and that trade-off isn't always worth it. Jenkins runs on your infrastructure, uses your secrets management, and follows your rules. For teams in regulated industries — finance, healthcare, defense — that level of control isn't a preference, it's a compliance requirement.
"But AI is built into the newer tools now!"
This is actually an interesting one, and it's where people assume Jenkins is frozen in 2012. It isn't. There's active development on an AI assistant plugin embedded directly into the Jenkins UI, designed to help with pipeline configuration, troubleshooting, and build analysis. The ecosystem is still moving. Old doesn't mean abandoned.
"But nobody uses Jenkins anymore..."
This one is just wrong. Jenkins is quietly running pipelines at some of the largest engineering organizations in the world. Over 2,000 plugins, a massive and battle-hardened community, and a Stack Overflow answer for almost every problem you'll encounter. The reason you don't hear about it is because it just works — and things that just work don't generate Twitter threads.
What Jenkins Actually Does Well
Jenkinsfile — Pipeline as Code
Your entire pipeline lives in a Jenkinsfile, versioned alongside your application code. No clicking through UIs, no configuration that only one person understands, no tribal knowledge walking out the door when someone quits. The pipeline is peer-reviewed, auditable, and reproducible.
Jenkins supports two pipeline syntaxes. Declarative gives you a clean, structured format that's easy to read and enforce across teams:
pipeline { agent { label 'linux' } environment { DOCKER_REGISTRY = 'registry.mycompany.com' APP_VERSION = "${BUILD_NUMBER}" } stages { stage('Build') { steps { sh 'mvn clean package -DskipTests' } } stage('Test') { parallel { stage('Unit Tests') { steps { sh 'mvn test' } } stage('Integration Tests') { steps { sh 'mvn verify -Pintegration' } } } } stage('Docker Build & Push') { steps { withCredentials([usernamePassword( credentialsId: 'docker-registry-creds', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS' )]) { sh ''' docker build -t $DOCKER_REGISTRY/myapp:$APP_VERSION . docker push $DOCKER_REGISTRY/myapp:$APP_VERSION ''' } } } stage('Deploy to Staging') { steps { sh 'kubectl set image deployment/myapp myapp=$DOCKER_REGISTRY/myapp:$APP_VERSION' } } stage('Deploy to Production') { input { message "Deploy to production?" } steps { sh './deploy.sh production $APP_VERSION' } } } post { success { slackSend channel: '#deployments', message: "Build deployed successfully" } failure { slackSend channel: '#deployments', message: "Build failed" } } } |
Scripted pipelines give you the full power of Groovy when you need conditional logic, dynamic stage generation, or anything a structured format can't express. Both approaches live in source control — no configuration drift, no mystery.
Master-Agent Architecture
One master orchestrates, many agents do the work. Agents can be connected over SSH, JNLP, or spun up dynamically via cloud plugins. You can distribute builds across machines, run stages in parallel, and route specific jobs to specific environments — a Windows agent for .NET builds, a Linux agent for containers, a GPU-equipped agent for ML pipelines.
Node labels give you precise routing:
pipeline { agent none stages { stage('Build on Windows') { agent { label 'windows && vs2022' } steps { bat 'msbuild MyApp.sln /p:Configuration=Release' } } stage('Run Tests on Linux') { agent { label 'linux && docker' } steps { sh 'pytest tests/ --junitxml=results.xml' } } stage('Deploy from Hardened Agent') { agent { label 'deploy && production-network' } steps { sh './deploy.sh' } } } } |
Need to scale dynamically? The Kubernetes plugin spins up a pod for each build and tears it down when it's done:
agent { kubernetes { yaml ''' apiVersion: v1 kind: Pod spec: containers: - name: maven image: maven:3.9-eclipse-temurin-17 command: ["sleep"] args: ["infinity"] resources: requests: memory: "2Gi" cpu: "1" - name: kaniko image: gcr.io/kaniko-project/executor:debug command: ["sleep"] args: ["infinity"] ''' defaultContainer 'maven' } } |
Every build gets a clean, isolated environment. No leftover state, no dependency conflicts between jobs, no "it works on my agent" problems.
Shared Libraries
Write your pipeline logic once and share it across every project in your organization. The library lives in its own Git repository, versioned independently, and loaded into any pipeline with a single annotation:
@Library('company-pipeline-lib@v2.1.0') _
pipeline { agent { label 'linux' } stages { stage('Security Scan') { steps { securityScan() } } stage('Build') { steps { buildDockerImage() } } stage('Push') { steps { pushToRegistry() } } stage('Deploy Staging') { steps { deployToEnv('staging') } } stage('Deploy Prod') { steps { deployToEnv('production', approval: true) } } } } |
One update to the library, and every pipeline across every project picks it up. No copy-pasting, no drift, no inconsistency between teams.
Credentials and Secrets Management
Jenkins has a built-in credentials store with role-based access control, supporting username/password pairs, SSH keys, secret files, secret text, and certificates. Credentials never appear in logs and are injected into builds at runtime:
withCredentials([ usernamePassword(credentialsId: 'aws-deploy', usernameVariable: 'AWS_KEY', passwordVariable: 'AWS_SECRET'), sshUserPrivateKey(credentialsId: 'prod-server-key', keyFileVariable: 'SSH_KEY'), string(credentialsId: 'sonar-token', variable: 'SONAR_TOKEN') ]) { sh ''' aws configure set aws_access_key_id $AWS_KEY aws configure set aws_secret_access_key $AWS_SECRET ssh -i $SSH_KEY deploy@prod.mycompany.com './deploy.sh' sonar-scanner -Dsonar.login=$SONAR_TOKEN ''' } |
For teams that need something more robust, Jenkins integrates natively with HashiCorp Vault, AWS Secrets Manager, and Azure Key Vault — pulling secrets dynamically at build time without ever storing them locally.
Build Triggers — More Than Just a Button
Jenkins supports a rich set of trigger mechanisms that go well beyond manually clicking "Build Now":
triggers { // Poll SCM every 5 minutes pollSCM('H/5 ')
// Scheduled nightly build at 2am cron('0 2 *')
// Triggered by upstream job completion upstream(upstreamProjects: 'build-core-library', threshold: hudson.model.Result.SUCCESS)
// Generic webhook trigger (GitHub, GitLab, Bitbucket) GenericTrigger( genericVariables: [[key: 'BRANCH', value: '$.ref']], causeString: 'Triggered by push to $BRANCH', token: 'my-secret-webhook-token', printContributedVariables: true ) } |
You can trigger a downstream deployment the moment a library build succeeds, kick off nightly regression suites on a cron schedule, or fire a build the instant a pull request is opened — all without polling for changes.
Test Reporting and Quality Gates
Jenkins parses JUnit, TestNG, and other test result formats natively, giving you trend graphs, failure history, and flaky test detection across builds. Combined with quality gates, you can fail a build automatically when coverage drops or new vulnerabilities are introduced:
stage('Test & Quality Gate') { steps { sh 'mvn test jacoco:report' withSonarQubeEnv('SonarQube') { sh 'mvn sonar:sonar' } } post { always { junit '**/target/surefire-reports/*.xml' jacoco(execPattern: '**/jacoco.exec', minimumLineCoverage: '80') } } }
stage('SonarQube Quality Gate') { steps { timeout(time: 5, unit: 'MINUTES') { waitForQualityGate abortPipeline: true } } } |
No manual checking. The pipeline either passes the gate or it doesn't ship.
Artifact Management
Build artifacts are archived natively and can be passed between stages or pushed to external repositories without manual intervention:
post { success { // Archive locally archiveArtifacts artifacts: 'target/*.jar, dist/**/*', fingerprint: true
// Push to Nexus nexusArtifactUploader( nexusVersion: 'nexus3', protocol: 'https', nexusUrl: 'nexus.mycompany.com', repository: 'releases', credentialsId: 'nexus-creds', artifacts: [[ artifactId: 'myapp', file: "target/myapp-${BUILD_NUMBER}.jar", type: 'jar' ]] )
// Or push to S3 s3Upload( bucket: 'my-artifact-bucket', path: "builds/${BUILD_NUMBER}/", includePathPattern: 'target/*.jar' ) } } |
Fingerprinting ties artifacts to the exact build that produced them. If something breaks in production, you can trace it back to a specific commit, build log, and test run in seconds.
Configuration as Code (JCasC)
Managing Jenkins configuration through the UI doesn't scale. The Configuration as Code plugin lets you define your entire Jenkins setup — agents, credentials, security settings, plugins, system configuration — in a single YAML file, checked into source control:
jenkins: systemMessage: "Production Jenkins — managed via JCasC" numExecutors: 0 agentProtocols: ["JNLP4-connect"] securityRealm: ldap: configurations: - server: ldap.mycompany.com rootDN: "dc=mycompany,dc=com" userSearchBase: "ou=users" authorizationStrategy: roleBased: roles: global: - name: "admin" permissions: ["Overall/Administer"] assignments: ["devops-team"] - name: "developer" permissions: ["Job/Build", "Job/Read", "Job/Workspace"] assignments: ["developers"] clouds: - kubernetes: name: "k8s-cloud" serverUrl: "https://k8s.mycompany.com" namespace: "jenkins-agents" credentialsId: "k8s-service-account" |
Spin up a new Jenkins instance, point it at this file, and it configures itself. Disaster recovery becomes a non-event.
Old Doesn't Mean Outdated

There's a pattern in the tech industry where tools get dismissed not because they stopped working, but because something shinier came along. Jenkins has been running production pipelines since 2011. The rough edges are known. The failure modes are documented. The fixes are a Google search away.
That's not a legacy burden. That's fifteen years of battle-testing.
The teams still running Jenkins in 2026 aren't behind — many of them made a deliberate choice, and it's paying off. They own their infrastructure, control their secrets, run builds exactly where they need to run, and have pipelines expressive enough to handle anything their software demands.
The tools that replace Jenkins will eventually be replaced themselves. Proven doesn't go out of style.
Still running Jenkins in your stack? Drop a comment — genuinely curious how others are using it in 2026, especially in regulated environments or large-scale distributed setups.





Comments