Applying Jenkinsfile Best Practices
1. Storing Jenkinsfile in SCM
| Aspect | Description | Best Practice |
|---|---|---|
| Location | Where the Jenkinsfile is stored | Store in the root of your source code repository |
| Version Control | How the Jenkinsfile is managed | Use Git or another SCM system to track changes |
| Branching Strategy | Handling Jenkinsfile in different branches | Keep Jenkinsfile in the same branch as the code it builds |
| Access Control | Permissions for Jenkinsfile | Restrict write access to authorized users only |
Example: Jenkinsfile in Git Repository
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building...'
}
}
}
}
Note: Storing Jenkinsfile in SCM ensures that the build process is versioned and can be reviewed, audited, and rolled back if needed.
2. Using Declarative vs Scripted Syntax
| Aspect | Declarative | Scripted |
|---|---|---|
| Syntax Style | Structured, pipeline-as-code format | Full Groovy scripting |
| Readability | Highly readable and intuitive | Can be complex and less readable |
| Flexibility | Less flexible, predefined structure | Highly flexible, full scripting power |
| Error Handling | Automatic error handling in stages | Manual error handling required |
| Use Case | Simple to moderately complex pipelines | Complex logic, dynamic behavior |
Example: Declarative Pipeline
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building...'
}
}
}
}
Choosing the Right Syntax
- Use Declarative for simpler, more structured pipelines with clear stages.
- Use Scripted for complex logic, dynamic behavior, or when full Groovy control is needed.
3. Implementing Pipeline as Code
| Aspect | Description | Best Practice |
|---|---|---|
| Declarative Pipeline Syntax | Uses a structured, readable syntax for defining pipelines. | Use declarative syntax for better readability and maintainability. |
| Scripted Pipeline Syntax | Allows more complex logic and control flow using Groovy. | Use scripted syntax for advanced use cases or dynamic behavior. |
| Jenkinsfile Location | Must be placed in the root of the project repository. | Keep Jenkinsfile in the root directory for easy access and version control. |
Example: Basic Declarative Pipeline
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building...'
}
}
}
}
Example: Scripted Pipeline with Conditional Logic
node {
if (env.BRANCH_NAME == 'main') {
echo 'Running on main branch'
} else {
echo 'Running on feature branch'
}
}
Note: Jenkinsfile should be treated as code and version-controlled alongside the source code.
4. Using Shared Libraries for Reusability
Shared libraries in Jenkins allow teams to define and reuse common pipeline logic across multiple jobs. They are particularly useful for standardizing build processes, reducing duplication, and improving maintainability.
| Feature | Description | Usage |
|---|---|---|
| library.name | Name of the shared library to be used in a pipeline. | @Library('my-shared-library') _ |
| library.version | Version or revision of the library to use. | @Library('my-shared-library@v1.0') _ |
| library.changelog | Indicates whether to include the changelog in the library usage. | library(changelog: true) |
Example: Using a Shared Library in Jenkinsfile
@Library('my-shared-library@v1.0') _
pipeline {
agent any
stages {
stage('Build') {
steps {
script {
myBuild()
}
}
}
}
}
Note: Shared libraries must be defined in Jenkins under
Manage Jenkins > Configure System under the Global Tool Configuration or via Jenkinsfile using library directive.
Best Practices for Shared Libraries
- Organize shared logic into modular functions or steps.
- Version libraries to ensure stability and prevent breaking changes.
- Use
librarydirective with specific versions to avoid unexpected behavior. - Document library usage and maintain a changelog for each version.
5. Implementing Error Handling
| Method | Description | Usage |
|---|---|---|
| try | Used to define a block of code to be tested for errors | |
| catch | Used to define a block of code to be executed if an error occurs in the try block | |
| finally | Used to define a block of code that will be executed regardless of whether an error occurred | |
| error | Used to throw an error in the pipeline | error 'Build failed' |
Example: Basic Error Handling in Jenkins Pipeline
pipeline {
agent any
stages {
stage('Build') {
steps {
script {
try {
sh 'echo "Starting build..."'
sh 'exit 1' // Simulate a failure
} catch (e) {
echo "Build failed with error: ${e}"
currentBuild.result = 'FAILURE'
} finally {
echo "Build process completed."
}
}
}
}
}
}
Example: Using Error to Fail Pipeline
pipeline {
agent any
stages {
stage('Deploy') {
steps {
script {
try {
sh 'echo "Deploying application..."'
sh 'exit 1' // Simulate deployment failure
} catch (e) {
error "Deployment failed: ${e}"
}
}
}
}
}
}
6. Using Proper Stage Naming
| Stage Name | Description | Best Practice |
|---|---|---|
| build | Compiles source code into executable binaries | Use descriptive names like build-backend or build-frontend |
| test | Runs unit and integration tests | Include test type in name: test-unit, test-integration |
| deploy | Deploys artifacts to target environments | Specify environment: deploy-staging, deploy-production |
| cleanup | Cleans up temporary files or resources | Use cleanup for general cleanup tasks |
Example: Stage Naming in Jenkinsfile
pipeline {
agent any
stages {
stage('build') {
steps {
sh 'make'
}
}
stage('test-unit') {
steps {
sh 'npm test'
}
}
stage('deploy-staging') {
steps {
sh 'ansible-playbook deploy.yml -e env=staging'
}
}
}
}
Note: Stage names should be descriptive and consistent across pipelines to improve readability and maintainability.
7. Implementing Timeout Strategies
| Strategy | Description | Configuration | Notes |
|---|---|---|---|
| timeout | Applies a timeout to the entire pipeline or a specific block | timeout(time: 10, unit: 'MINUTES') { ... } |
Can be applied to stages or the entire pipeline |
| timeout.time | Duration of the timeout in specified units | time: 30 |
Valid units: SECONDS, MINUTES, HOURS, DAYS |
| timeout.unit | Time unit for the timeout value | unit: 'HOURS' |
Default is MINUTES if not specified |
Example: Timeout on a Stage
pipeline {
agent any
stages {
stage('Build') {
timeout(time: 10, unit: 'MINUTES') {
steps {
sh 'make'
}
}
}
}
}
Example: Timeout on Entire Pipeline
pipeline {
agent any
timeout(time: 2, unit: 'HOURS') {
stages {
stage('Build') {
steps {
sh 'make'
}
}
stage('Test') {
steps {
sh 'make test'
}
}
}
}
}
Note: If a timeout occurs, the pipeline will be marked as failed and any ongoing steps will be terminated.
8. Using Cleanup Actions
Cleanup actions in Jenkins are essential for maintaining a clean and efficient build environment. They help remove temporary files, artifacts, and other resources that are no longer needed after a build completes.
| Action | Description | Usage Example |
|---|---|---|
| clean() | Removes the workspace directory before the build starts. | |
| deleteDir() | Deletes the entire workspace directory at the beginning of a stage. | |
| post | Executes actions after a build completes, regardless of success or failure. | |
Example: Using cleanWs() in Post Section
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'mkdir -p build/output'
sh 'echo "Building project..." > build/output/log.txt'
}
}
}
post {
always {
cleanWs()
}
}
}
Note: The
cleanWs() step removes all files in the workspace, including any artifacts or logs that might be needed for debugging. Use it carefully and ensure that important data is archived before cleanup.
9. Optimizing Pipeline Performance
| Optimization Area | Description | Best Practice |
|---|---|---|
| Parallel Stages | Running stages in parallel to reduce total execution time | Use parallel directive for independent tasks |
| Resource Allocation | Efficient use of agents and executors | Use agent none for non-agent steps |
| Caching Strategies | Reusing artifacts or dependencies to avoid redundant work | Use archiveArtifacts and stash for caching |
| Pipeline Caching | Reusing pipeline steps or stages across builds | Implement cache blocks for expensive operations |
Example: Parallel Execution
pipeline {
agent any
stages {
stage('Build') {
parallel {
stage('Frontend') {
steps {
echo 'Building frontend...'
}
}
stage('Backend') {
steps {
echo 'Building backend...'
}
}
}
}
}
}
Example: Caching Artifacts
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'npm install'
sh 'npm run build'
archiveArtifacts artifacts: 'dist/**', fingerprint: true
}
}
stage('Deploy') {
steps {
stash name: 'dist', includes: 'dist/**'
}
}
}
}
10. Documenting Pipeline Logic
| Element | Description | Usage |
|---|---|---|
| pipeline | Defines the Jenkins Pipeline | pipeline { ... } |
| agent | Specifies where the pipeline runs | agent any |
| stages | Contains all the stages of the pipeline | stages { ... } |
| stage | Represents a single stage in the pipeline | stage('Build') { steps { ... } } |
| steps | Defines actions to be executed in a stage | steps { ... } |
| parameters | Allows passing parameters to the pipeline | parameters { ... } |
| environment | Defines environment variables for the pipeline | environment { ... } |
| options | Configures pipeline options | options { ... } |
Example: Basic Pipeline Structure
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building...'
}
}
}
}
Note: Use descriptive names for stages and include comments to explain complex logic.