Jenkins jobs are usually created manually, but for large-scale deployments or dynamic environments, this is unsustainable. The Job DSL plugin automates Jenkins job creation, allowing you to define jobs as code. This means version control, reproducibility, and programmatic updates for your entire Jenkins job catalog.

Here’s a Jenkins instance with a few jobs already set up:

# Jenkins UI - Manage Jenkins -> Configure System
# This is where you'd set up SCMs, global tools, etc.
# For this example, we'll assume a simple setup.

# A typical Jenkins job might look like this in its XML config:
# <com.cloudbees.hudson.plugins.groovy.ProjectScmCoordinator plugin="job-dsl@1.87">
#   <scm class="hudson.scm.NullSCM"/>
# </com.cloudbees.hudson.plugins.job-dsl.ProjectScmCoordinator>
# <builders>
#   <hudson.tasks.Shell>
#     <command>echo "Hello from a generated job!"</command>
#   </hudson.tasks.Shell>
# </builders>
# ... other configurations ...

The core idea is to have a "seed job" that, when run, executes a Groovy script. This script uses the Job DSL API to define new Jenkins jobs.

Let’s set up a seed job.

  1. Install the Job DSL Plugin: Go to Manage Jenkins -> Manage Plugins -> Available and search for "Job DSL". Install it.

  2. Create a Seed Job:

    • Go to New Item -> Freestyle project. Name it job-dsl-seed.
    • Under Build, select Process Job DSLs.
    • DSL Script: Choose Use a script from SCM or Look on Jenkins (file or text). For simplicity, let’s use Look on Jenkins (file or text).
    • In the Script text area, paste the following Groovy script:
    // job-dsl-seed.groovy
    def jobsToCreate = [
        [name: 'hello-world-job-1', message: 'Hello from job 1!'],
        [name: 'hello-world-job-2', message: 'Greetings from job 2!']
    ]
    
    jobsToCreate.each { jobInfo ->
        job(jobInfo.name) {
            description("A dynamically generated job for ${jobInfo.name}")
            scm {
                git {
                    remote {
                        url('https://github.com/jenkinsci/job-dsl-plugin.git') // Example repo
                        branches('main')
                    }
                }
            }
            steps {
                shell("echo \"${jobInfo.message}\"")
            }
            publishers {
                archiveArtifacts("*.log")
            }
        }
    }
    
  3. Save and Build: Save the seed job and click Build Now.

After the build completes, you’ll see two new jobs, hello-world-job-1 and hello-world-job-2, listed on your Jenkins dashboard. If you look at their configurations, you’ll see they’ve been created with the specified description, SCM, and shell steps.

The mental model is that the seed job is a generator. It doesn’t do the work itself; it creates other jobs that do the work. The Job DSL script is the blueprint for these generated jobs.

The real power comes when you store your DSL scripts in a Version Control System (VCS) like Git.

  1. Modify the Seed Job:

    • Edit your job-dsl-seed job.
    • In the Process Job DSLs section, change Look on Jenkins to Use a script from SCM.
    • SCM: Select Git.
    • Repository URL: Enter the URL of a Git repository.
    • Credentials: Add any necessary credentials.
    • Branches to build: Specify */main (or your branch).
    • Script Path: Enter the path to your Groovy script within the repository (e.g., jenkins/jobs.groovy).
  2. Create jenkins/jobs.groovy in your Git Repo:

    // jenkins/jobs.groovy
    // This script is now in Git and will be executed by the seed job.
    
    def serviceJobs = [
        [name: 'backend-service-deploy', repo: 'git@github.com:myorg/backend-service.git', branch: 'main'],
        [name: 'frontend-service-deploy', repo: 'git@github.com:myorg/frontend-service.git', branch: 'develop']
    ]
    
    serviceJobs.each { service ->
        job(service.name) {
            description("Deploys the ${service.name} service")
            scm {
                git {
                    remote {
                        url(service.repo)
                        branches(service.branch)
                    }
                }
            }
            triggers {
                // Example: Trigger on changes to the job config itself
                scm()
            }
            steps {
                maven('clean install') {
                    // Configure Maven settings if needed
                }
                shell('docker build -t myregistry/${JOB_NAME}:${BUILD_ID} .')
                shell('docker push myregistry/${JOB_NAME}:${BUILD_ID}')
            }
            // Example: Parameterized job
            parameters {
                stringParam('DEPLOY_ENV', 'staging', 'Deployment environment (staging/production)')
            }
        }
    }
    
  3. Commit and Build: Commit this script to your Git repository. Then, trigger a build of the job-dsl-seed job. Jenkins will fetch the script and create backend-service-deploy and frontend-service-deploy jobs.

The job() block is the fundamental unit. Inside it, you can configure almost any aspect of a Jenkins job. The scm {}, steps {}, triggers {}, and parameters {} blocks are just a few examples. You can also define jobs as pipelineJob, multibranchPipeline, etc., using similar DSL syntax.

The most surprising thing about Job DSL is how it blurs the lines between Jenkins configuration and application code. You’re not just configuring Jenkins; you’re programming your Jenkins instance. This means you can use standard programming constructs like loops, conditionals, and functions (or Groovy def methods) to generate complex job structures. For instance, you could write a function that takes a list of microservices and generates a build, test, and deploy job for each, all parameterized by their respective Git repositories and branches.

What people often miss is the ability to remove jobs programmatically. If a job defined by your DSL script is no longer present in the script, running the seed job will not automatically delete it. You need to explicitly manage job lifecycle. To achieve deletion, you can use the cleanupJobs() block within your seed job’s DSL script. This block tells the Job DSL plugin to find jobs that are not defined in the current DSL script and remove them. This is crucial for maintaining a clean job catalog when your application infrastructure evolves.

The next step is often managing complex pipeline configurations using the Pipeline DSL, which is a related but distinct plugin.

Want structured learning?

Take the full Jenkins course →