Jenkins Pipeline Basics: Write Production-Grade CI/CD That Won't Burn You at 3 AM
Jenkins Pipeline basics explained with real production patterns.
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
A Jenkins Pipeline is a set of steps defined in a Jenkinsfile that automates building, testing, and deploying your code. Use Declarative syntax for most projects; switch to Scripted only when you need complex logic that Declarative can't express.
Think of a Jenkins Pipeline as a recipe card for your software. The recipe lists steps: 'mix ingredients' (checkout code), 'bake at 350°F' (run tests), 'let cool' (deploy). You write the recipe once, check it into version control, and Jenkins follows it every time. If the recipe breaks, you fix the card, not the oven.
Most Jenkins pipelines I see in production are a house of cards. One plugin update, one Groovy method that silently returns null, and your entire deployment chain collapses at 2 AM. I've debugged enough of these to know: the problem isn't Jenkins — it's how people write pipelines.
The core issue? Developers treat pipelines as disposable glue code. They copy-paste from Stack Overflow, use Scripted syntax when Declarative would do, and never test the pipeline itself. Then they wonder why a production deploy fails because a variable wasn't initialized.
By the end of this article, you'll be able to write a Declarative pipeline that handles parallel stages, credentials, and error recovery — and you'll know exactly when to reach for Scripted instead. No fluff, just patterns that survive production.
Why Declarative Pipelines Are Your Default — and When They're Not
Declarative syntax forces structure. You define stages, steps, and post-actions in a clean block hierarchy. It's easier to read, validate, and debug. Scripted pipelines give you full Groovy power — but that power comes with serialization issues, harder debugging, and a tendency to turn into spaghetti. I've seen teams spend weeks debugging a Scripted pipeline that could have been 20 lines of Declarative. Start Declarative. Only reach for Scripted when you need conditional stage execution based on complex logic, dynamic parallel branches, or custom step definitions that can't be expressed in Declarative's script block.
def list = [] that gets populated inside a node block — later, when Jenkins tries to checkpoint the pipeline, you get NotSerializableException. Fix: use @NonCPS on methods that hold non-serializable state, or better, use Declarative.Environment Variables and Credentials — Don't Hardcode, Don't Leak
Hardcoding secrets in your Jenkinsfile is a firing offense. Use Jenkins credentials store and the withCredentials step. For environment-specific values (API URLs, feature flags), use environment blocks with credentials() or load from a vault. Never echo a credential — even in a debug step. I've seen a team accidentally print a production AWS secret key to build logs because they used sh 'env' in a debug stage. The logs were public.
echo or sh 'env' in a pipeline that has credentials. Even if you mask the secret, the log might be stored and searchable. Use withCredentials and only reference the variable inside the block.Parallel Stages — Speed Up Builds Without Breaking the Bank
Parallel stages let you run independent tasks concurrently — like running unit tests and linting at the same time. But don't go overboard. Each parallel branch consumes an executor. If your Jenkins has 4 executors and you run 10 parallel branches, 6 will queue. Use failFast true to abort all branches if one fails — otherwise you wait for all to finish even if one already failed. I've seen a build take 30 minutes because a failed parallel branch didn't abort the others.
parallel with a loop. But keep it simple: Declarative's parallel block is enough for 90% of cases.Post Actions — Always Clean Up, Always Notify
The post block runs after all stages, regardless of success or failure. Use it to clean up resources (kill containers, delete temp files) and send notifications (Slack, email). Never put cleanup in a finally block inside a stage — if the stage crashes, finally might not run. The post block is guaranteed to execute. I've seen a pipeline leave behind 50GB of Docker images because the cleanup was in a stage that got aborted.
post and a try-finally block in a stage?' Answer: post runs even if the stage is aborted by a user or timeout. finally inside a script block may not run if the JVM crashes or the executor is killed. Always use post for critical cleanup.Shared Libraries — Don't Repeat Yourself, Don't Repeat Your Pipeline
If you have multiple pipelines doing similar things (e.g., building Docker images, deploying to Kubernetes), extract the logic into a shared library. A shared library is a separate Git repo with Groovy classes and vars — global functions you can call from any Jenkinsfile. This keeps your Jenkinsfiles thin and your logic testable. I've seen teams with 50 Jenkinsfiles that all had the same 100 lines of deployment code. One bug fix required editing all 50 files. Use shared libraries.
Error Handling — Don't Let a Flaky Test Block a Deploy
Use catchError to mark a stage as unstable instead of failing the whole pipeline. For example, if a flaky integration test fails, you might want to continue and let the team review later. But use this sparingly — if you catch every error, you'll mask real failures. I've seen a team that caught all errors and then wondered why broken code reached production. The rule: catch only known flaky steps, and always notify.
try-catch and ignore failures, you'll deploy broken code. Only catch specific steps that are known to be flaky. Use catchError with buildResult: 'UNSTABLE' so the pipeline still shows a warning.When Not to Use Jenkins Pipelines — Simpler Alternatives
Jenkins Pipelines are powerful, but they're overkill for simple projects. If your CI/CD needs are just 'run tests and deploy to a single server', consider GitHub Actions, GitLab CI, or even a shell script triggered by a webhook. Jenkins requires maintenance — plugin updates, JVM tuning, executor management. I've seen a startup spend more time maintaining Jenkins than writing code. Use Jenkins when you need complex workflows, multiple environments, or compliance requirements (audit logs, approval gates). Otherwise, keep it simple.
The Pipeline That Deleted the Database
sh 'dropdb myapp_staging' as a cleanup step. The when condition was misconfigured — it ran on every branch, not just feature branches. The when block used branch 'feature/*' but the actual branch was feature/foo — the glob pattern was wrong.when condition to expression { return env.BRANCH_NAME.startsWith('feature/') } and added a manual approval step with input before any destructive action.- Never run destructive operations without explicit human approval and a double-checked
whencondition. - Test your
whenlogic with a dry-run stage first.
java.io.NotSerializableException on a variable@NonCPS if it doesn't need to survive restarts. 3. Or convert the variable to a serializable type (e.g., use String instead of GString).@Library('lib')_ and the library is trusted.Key takeaways
withCredentialspost blocks, not in stage-level finally. post is guaranteed to run even on abort.Interview Questions on This Topic
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
That's Jenkins. Mark it forged?
3 min read · try the examples if you haven't