Jenkins - concepts
What is Jenkins?
Jenkins is a Java-based automation server. It runs jobs - units of work like building code, running tests, or deploying artifacts. Jobs are triggered by events (git push, PR opened, timer, manual click) and run on agents (machines that execute the work). The Jenkins controller orchestrates everything.
Core concepts
Jobs
A job is a configured task. Types include:
Freestyle - simple, UI-configured build/test/deploy
Pipeline - defined by a Jenkinsfile (config as code)
Multibranch pipeline - one pipeline per branch/PR, auto-discovered from a source (GitHub, Git, …)
Folder - groups jobs together
Seed job - a job that creates OTHER jobs (via the Job DSL plugin)
Pipelines, agents, plugins
A pipeline is a series of stages (logical groups like “Build”, “Test”)
containing steps (sh, checkout, archiveArtifacts). Pipelines
run on nodes (agents) - physical, VM, container, or Kubernetes pods,
connected via SSH or JNLP. Jenkins is minimal out of the box; almost everything
comes from plugins (Job DSL, GitHub Branch Source, Pipeline, Branch API…).
Jenkinsfiles
A Jenkinsfile is a Groovy script that defines a pipeline, stored in the repo alongside the code it builds. Two flavors:
Scripted - raw Groovy, full language power, older style:
node {
stage('Build') {
checkout scm
sh 'make build'
}
stage('Test') {
sh 'make test'
}
}
Declarative - structured syntax, easier to read, more guardrails:
pipeline {
agent any
stages {
stage('Build') { steps { sh 'make build' } }
stage('Test') { steps { sh 'make test' } }
}
}
Jenkins configuration - XML under the hood
Every job is stored as an XML file on disk; the UI is just a form editor for it. You can view/edit the XML directly:
UI: job page → Configure
REST API:
curl <jenkins>/job/<name>/config.xmlOn disk:
$JENKINS_HOME/jobs/<name>/config.xml
Job DSL plugin
Job DSL lets you define jobs as Groovy code instead of clicking through the UI. The plugin translates DSL calls into Jenkins XML.
multibranchPipelineJob('afterburn') {
branchSources {
branchSource {
source {
github {
repoOwner('coreos')
repository('afterburn')
}
}
}
}
}
A seed job (a regular pipeline/freestyle job) runs these DSL scripts via
jobDsl steps.
Static vs Dynamic DSL
Static DSL - hand-written Groovy context classes in the job-dsl-plugin repo (
github {},git {}…). Always available.Dynamic DSL - auto-generated at runtime from installed plugins via Java reflection. Only available inside Jenkins; tagged purple “Dynamic” in the API viewer at
<jenkins>/plugin/job-dsl/api-viewer/index.html.configure block - escape hatch for raw XML when neither exposes what you need. Fragile, no validation.
Constructors and setters (Java basics)
Reading plugin source means reading Java. A constructor runs once when an object is created; its params are required. A setter configures an optional property after creation.
public class GitHubSCMSource {
public GitHubSCMSource(String repoOwner, String repository,
String repositoryUrl, boolean configuredByUrl) {
this.repoOwner = repoOwner; // required to create the object
this.repository = repository;
}
public void setCredentialsId(String credentialsId) {
this.credentialsId = credentialsId; // optional - skip for null
}
}
For Job DSL: constructor params become required DSL methods; setter methods become optional ones.
Stapler annotations
Plugins use the Stapler framework. Three annotations control how classes become configurable:
@DataBoundConstructor- marks the constructor whose params come from the form / DSL (one per class, all required).@DataBoundSetter- marks setter methods for optional params.@Symbol- gives a class a short DSL name; when present it replaces the camelCase class name entirely.
A class must implement Describable (usually via AbstractDescribableImpl)
for the dynamic DSL to generate methods for it.
How the dynamic DSL generates methods
For each Describable class with @DataBoundConstructor, the plugin
generates a DSL method:
Method name =
@Symbolvalue if present, else camelCase class name.Required properties =
@DataBoundConstructorparams.Optional properties =
@DataBoundSettermethods.Nesting = if a param/setter type is itself
Describable, it becomes a nested closure.
The @ContextType annotation on a DSL context class tells the dynamic DSL
which Describable types to look for in that context.
DSL calls are nested closures, not dot-chains
multibranchPipelineJob('afterburn') { // delegate: MultibranchWorkflowJob
branchSources { // delegate: BranchSourcesContext
branchSource { // delegate: BranchSource (dynamic)
source { // delegate: SCMSource context
github { // delegate: GitHubSCMSource context
configuredByUrl(false)
}
}
}
}
}
Each {} is a new scope with its own delegate. When you call
configuredByUrl(false) inside github {}, Groovy resolves it against the
closure’s delegate (Closure.DELEGATE_FIRST): owner first? no - delegate
(the dynamic context for GitHubSCMSource)? yes, it records the value. You
can’t write github.configuredByUrl(false) because github isn’t an object
you hold - it’s a method that takes a closure. The nesting IS the structure:
moving a call to the wrong level calls it on the wrong delegate and either
silently does nothing or throws MissingMethodException.
The configure block (raw XML)
configure {
it / sources / data / 'jenkins.branch.BranchSource' / strategy { ... }
}
it= root XML node of the job config/= navigate to (or create) a child element, like XPath{ ... }and<<= APPEND children to the found node
Pitfall: / returns the existing node if found, and closures append
rather than replace - if the static DSL already created a <traits> node and
your configure block appends another, you get duplicate XML.
Multibranch pipeline - key concepts
A multibranch pipeline auto-creates a sub-job for each branch/PR. Four layers:
Branch sources - which repo to scan and with what credentials (
GitHubSCMSource,GitSCMSource).Traits - what to discover:
BranchDiscoveryTrait(regular branches),OriginPullRequestDiscoveryTrait(same-repo PRs),ForkPullRequestDiscoveryTrait(fork PRs, with a security-criticaltrustpolicy:TrustNobody/TrustContributors/TrustPermission/TrustEveryone).Strategy - a
BranchPropertyStrategyapplies properties to discovered branches (e.g.DurabilityHintBranchProperty).Build strategies - when to build.
ChangeRequestBuildStrategyImpl(buildChangeRequests) hasignoreTargetOnlyChangesandignoreUntrustedChanges.
Migrating a configure block to native DSL
A repeatable approach when replacing a fragile configure block:
Understand what the old block patches - e.g. the traits, strategy and buildStrategies XML sections.
Check the static DSL - read the job-dsl-plugin source; a helper like
github {}may expose trait flags but notstrategy/buildStrategiesif its context isn’t extensible.Check the dynamic DSL - confirm the class is
Describablewith the relevant@DataBoundSettermethods; if so,branchSource {}gives full access.Mind parameter differences - the dynamic
github {}(insidesource {}) differs from the static one (insidebranchSources {}):credentialsIdvscheckoutCredentialsId, traits set directly rather than via boolean flags, and a constructor requiringrepositoryUrl/configuredByUrl.