Pipelines as Code
Pipeline logic — the stages, steps, and shared behavior a build runs — lives in source control as code, not configured through the Jenkins UI.
A Jenkinsfile in source control survives a controller rebuild; a job configured entirely through the UI does not. Treat pipeline definitions as code, reviewed the same way application code is. Once more than a couple of pipelines share the same stages, a shared library keeps that logic in one place instead of drifting across Jenkinsfiles as each one is copy-pasted and separately edited.
Example: a basic declarative pipeline
Section titled “Example: a basic declarative pipeline”Nothing exotic — the point is that all of it, including the failure notification, lives in the file next to the code it builds, not configured by hand on the job’s UI page:
pipeline { agent { label 'linux' }
options { timestamps() disableConcurrentBuilds() }
stages { stage('Build') { steps { sh 'make build' } } stage('Test') { steps { sh 'make test' } } }
post { failure { slackSend(channel: '#ci-alerts', message: "Build failed: ${env.BUILD_URL}") } }}Example: shared library + a dynamic Kubernetes agent
Section titled “Example: shared library + a dynamic Kubernetes agent”Once a pipeline needs more than a label to describe its agent, agent { label 'linux' } stops being enough — see Dynamic Kubernetes Agents. Pulling in a shared library and requesting a composed pod template keeps that complexity out of every individual Jenkinsfile:
@Library('k8sagent') _
pipeline { agent { kubernetes(k8sagent(name: 'small+postgres')) }
stages { stage('Integration test') { steps { container('postgres') { sh 'pg_isready -h localhost' } container('jnlp') { sh 'make integration-test' } } } }}The small+postgres label is composed at runtime from separate template fragments (size, Postgres sidecar) rather than one hand-maintained pod spec per combination — the pipeline just declares what it needs, not how the pod gets built. This is also a good example of a shared library doing real work: the Jenkinsfile stays about what to test, not how to provision the thing it tests against.
Related
- Bounding growth
- Concourse: config as code — Concourse makes this non-optional; Jenkins makes it a discipline worth holding yourself to.