- Use Declarative Pipeline syntax (pipeline { }) over Scripted Pipeline — enforces structure, validates at parse time
- Keep the Jenkinsfile in the repository root, versioned alongside application code
- Name stages descriptively: stage('Build'), stage('Unit Tests'), stage('Deploy to Staging')
- Set options { timeout(time: 30, unit: 'MINUTES') } to prevent hung builds
- Use agent directives per stage: agent { docker { image 'node:20-alpine' } }
- Use Declarative Pipeline syntax (pipeline { }) over Scripted Pipeline — validates before execution, integrates with Blue Ocean
- Store Jenkinsfile in repo root — never rely on inline pipeline definitions in the Jenkins UI
- Name stages clearly: stage('Build'), stage('Integration Tests'), stage('Deploy to Production')
- Set options { timeout(time: 30, unit: 'MINUTES') } globally and per-stage
- Use agent none at pipeline level when stages need different agents; specify agent per stage
- Define environment blocks at both pipeline and stage level to scope variables
- Use parameters block for parameterized builds
- Apply when directives: when { branch 'main' }, when { changeset '**/*.java' }
- Use options { disableConcurrentBuilds() } on deployment pipelines
- Push complex logic into shared library functions, not inline Groovy
- Add options { buildDiscarder(logRotator(numToKeepStr: '20')) }