Jenkins Pipeline (formerly Workflow) allows defining the entire CI/CD build, test, and deploy process as code in a Jenkinsfile committed to the source repository alongside the application code. This “Pipeline as Code” approach means the build process is version-controlled, reviewable via pull requests, and reproducible across environments. Jenkins supports two Pipeline syntaxes: Declarative Pipeline (structured, opinionated format recommended for most use cases) and Scripted Pipeline (Groovy-based, flexible for complex logic). A Declarative Pipeline describes the pipeline in a series of stages with explicit steps, environment variables, and conditions. This guide covers writing Jenkinsfiles for Node.js and Docker-based projects on RHEL 9, using Jenkins agents, and managing build credentials securely.
Prerequisites
- Jenkins LTS installed on RHEL 9
- Jenkins plugins: Pipeline, Git, Docker Pipeline (install via Manage Jenkins → Plugins)
Step 1 — Basic Declarative Jenkinsfile
# Jenkinsfile — place in the root of the Git repository
pipeline {
agent any # Run on any available Jenkins agent
environment {
APP_NAME = 'myapp'
NODE_ENV = 'test'
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'https://github.com/myorg/myapp.git'
}
}
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Test') {
steps {
sh 'npm test -- --reporter=junit'
}
post {
always {
junit 'test-results/*.xml' # Publish test results
}
}
}
stage('Build') {
steps {
sh 'npm run build'
}
}
stage('Deploy') {
when {
branch 'main' # Only deploy from main branch
}
steps {
sh 'rsync -av dist/ deploy@production-server:/var/www/html/'
}
}
}
post {
failure {
emailext to: '[email protected]',
subject: "BUILD FAILED: ${currentBuild.fullDisplayName}",
body: "Check console output at ${env.BUILD_URL}"
}
}
}
Step 2 — Docker Build and Push Pipeline
# Jenkinsfile for Docker image build and push
pipeline {
agent any
environment {
REGISTRY = 'registry.example.com'
IMAGE = "${REGISTRY}/myapp"
TAG = "${env.BUILD_NUMBER}-${env.GIT_COMMIT[0..6]}"
}
stages {
stage('Build Image') {
steps {
script {
docker.build("${IMAGE}:${TAG}")
}
}
}
stage('Push Image') {
steps {
script {
// 'registry-credentials' is a Jenkins credential ID
docker.withRegistry("https://${REGISTRY}", 'registry-credentials') {
docker.image("${IMAGE}:${TAG}").push()
docker.image("${IMAGE}:${TAG}").push('latest')
}
}
}
}
stage('Deploy to Kubernetes') {
steps {
withKubeConfig([credentialsId: 'k8s-admin']) {
sh "kubectl set image deployment/myapp myapp=${IMAGE}:${TAG} -n production"
sh "kubectl rollout status deployment/myapp -n production"
}
}
}
}
}
Step 3 — Store and Use Credentials
# In the Jenkinsfile, access credentials via withCredentials():
stage('Deploy') {
steps {
withCredentials([
usernamePassword(
credentialsId: 'deploy-ssh-credentials',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh 'sshpass -p "$SSH_PASS" rsync -av dist/ $SSH_USER@server:/var/www/'
}
}
}
Conclusion
Jenkins Declarative Pipelines on RHEL 9 provide a readable, version-controlled CI/CD workflow. The most impactful Pipeline practices are: always use the when { branch 'main' } directive to prevent accidental deployments from feature branches; store all credentials in the Jenkins Credentials Manager (never hardcode them in Jenkinsfiles); and use the post { always {} } block to ensure test result archiving and notifications happen even when the build fails. The BUILD_NUMBER + short Git commit hash as the Docker image tag provides a unique, traceable identifier for every built image.
Next steps: How to Install Jenkins on RHEL 9, How to Install GitLab CE on RHEL 9, and How to Register a GitHub Actions Runner on RHEL 9.