Day 6: Jenkins Pipeline: Build, Push, Deploy | Project-Based Approach
This chapter covers the end-to-end migration of a legacy Jenkins Freestyle job into a declarative Jenkinsfile managed under Source Code Management (SCM). We explore the declarative syntax tree, credential binding mechanisms, multi-stage workflows (Checkout, Build, Push, Deploy, Test, Cleanup), build artifact archiving, fingerprint tracking, post actions, and production-grade workspace hygiene.
1. Why Transition from Freestyle Jobs to Jenkins Pipelines?
Freestyle jobs provide an intuitive graphical interface for running CI/CD tasks, but they introduce severe limitations in enterprise engineering environments:
- Lack of Version Control: Configuration changes made inside the Jenkins web UI are mutable and untracked. There is no native code review (
Pull Request), history diffing, or automated rollback for Freestyle configurations. - Inability to Branch and Modularize: A Freestyle build enforces a monolithic execution sequence. It does not provide clear stage separation, visualization of multi-step lifecycles, or programmatic conditional branching.
- Disaster Recovery Overhead: If the Jenkins controller crashes or state corrupts, reconstructing dozens of Freestyle jobs manually from UI memory is error-prone.
- Configuration as Code (CasC): A pipeline defined as code (
Jenkinsfile) lives directly with the application source code. It is versioned, reviewed, tested, and portable across Jenkins instances.
2. Core Architecture and Pipeline Mental Model
A Jenkins Declarative Pipeline follows a strictly nested block hierarchy. Understanding this tree prevents syntax and indentation errors.
The Declarative Hierarchy
pipeline: The required top-level wrapper block containing the entire workflow definition.agent: Specifies where the pipeline executes.agent any: Allocates any available executor node (controller or worker agent).agent { label 'docker' }: Schedules the workload onto an agent matching a specific label tag (e.g., dedicated Docker/Maven daemons).
stages: The structural envelope encapsulating all individual pipeline milestones.stage('Name'): An isolated logical segment displayed in the Jenkins Blue Ocean / Stage View visualization console. If a stage fails, execution halts immediately by default.steps: The execution block inside astagecontaining one or more direct tasks, CLI shell commands, or plugin wrappers.
3. Step-by-Step Evolution: Building the Pipeline Syntax
Step 1: The Minimal Pipeline Skeleton
To establish baseline syntactical correctness, begin with an empty scaffold using built-in echo steps.
pipeline {
agent any
stages {
stage('Checkout') {
steps {
echo 'Checking out source code from private repository...'
}
}
stage('Build') {
steps {
echo 'Building Docker container image...'
}
}
stage('Push') {
steps {
echo 'Pushing image to Docker Hub container registry...'
}
}
stage('Deploy') {
steps {
echo 'Deploying container onto host machine...'
}
}
stage('Test') {
steps {
echo 'Executing HTTP smoke tests against port 5000...'
}
}
}
}
4. Source Application and Dockerfile Architecture
The project deploys a lightweight Python Flask service encapsulated within a container.
The Application Code (app.py)
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Welcome to cloud with varos: Built with Flask, shipped with Jenkins, running in Docker."
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Production Optimizations in the Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Copy dependency definition first to leverage Docker layer caching
COPY requirements.txt .
# Install dependencies without keeping local download caches
RUN pip install --no-cache-dir -r requirements.txt
# Copy source code
COPY app.py .
# Metadata documentation
EXPOSE 5000
# Container entrypoint
CMD ["python", "app.py"]
Note on
--no-cache-dir: By default,pipretains.tar.gzand wheel caches inside~/.cache/pip. Suppressing this via--no-cache-direliminates unnecessary build bloat, drastically shrinking image size.
5. Integrating Built-in Steps vs. Shell Execution (sh)
Jenkins pipelines categorize operations into two runtime formats:
- Built-in Plugin Steps: Native Groovy functions executed directly by Jenkins core or plugins (e.g.,
git,archiveArtifacts,cleanWs,withCredentials). These do not spawn a host shell process. - External Shell Steps (
sh): Explicit CLI commands dispatched to the underlying host or agent shell (Unix/Linux/bin/shor/bin/bash).
6. Secure Credential Handling and Secret Binding
Hardcoding passwords or personal access tokens (PAT) in plain text inside a Jenkinsfile is a critical security vulnerability. Jenkins provides the Pipeline Syntax Snippet Generator (<jenkins-url>/job/<job-name>/pipeline-syntax/) to generate safe credential-binding wrappers.
Pipeline Syntax Snippet Generator Workflow
withCredentials([usernamePassword(
credentialsId: 'cloudwithvarun-dockerhub',
usernameVariable: 'DOCKERHUB_USER',
passwordVariable: 'DOCKERHUB_PWD'
)]) {
// Jenkins masks these values with **** in console outputs
sh 'echo "$DOCKERHUB_PWD" \vert docker login -u "$DOCKERHUB_USER" --password-stdin'
sh 'docker push $IMAGE:$TAG'
sh 'docker push $IMAGE:latest'
}
Security Note on
--password-stdin: Runningdocker login -u user -p passwordexposes credentials in system process listings (ps aux). Piping via standard input (echo "DOCKERHUB_USER" --password-stdin) prevents process-level credential exposure.
7. Build Traceability: Metadata Generation and Artifact Archiving
In continuous delivery systems, code compiled or packaged in early stages must be tracked as it advances across environments.
Metadata Generation Script
Within the deploy stage, metadata is dynamically populated via shell and environment interpolation:
sh """
echo "BUILD_NUMBER=${BUILD_NUMBER}" > deploy-info-${BUILD_NUMBER}.txt
echo "IMAGE=${IMAGE}:${TAG}" >> deploy-info-${BUILD_NUMBER}.txt
echo "GIT_COMMIT=\$(git rev-parse --short HEAD)" >> deploy-info-${BUILD_NUMBER}.txt
echo "GIT_BRANCH=${GIT_BRANCH}" >> deploy-info-${BUILD_NUMBER}.txt
echo "TIMESTAMP=\$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> deploy-info-${BUILD_NUMBER}.txt
echo "BUILD_URL=${BUILD_URL}" >> deploy-info-${BUILD_NUMBER}.txt
"""
Archiving and Fingerprinting
The archiveArtifacts step preserves files produced by a build outside of the mutable workspace:
archiveArtifacts artifacts: "deploy-info-${BUILD_NUMBER}.txt", fingerprint: true
artifacts: File pattern relative to the workspace root.fingerprint: true: Computes an MD5 checksum of the file and stores it in Jenkins’ global database. If this artifact is downloaded or consumed by a downstream deployment pipeline, Jenkins maintains a complete dependency and usage history.
8. Post Actions and Automated Workspace Hygiene
Post Action Hooks
Declarative pipelines provide the post block to define finalizer actions based on the execution status of the pipeline:
always: Runs unconditionally, regardless of build result (ideal for notifications or cleanup).success: Runs only if all preceding stages completed without error.failure: Runs if any stage failed, threw an exception, or aborted.unstable: Runs when tests fail but compilation succeeded.
post {
always {
echo "Build pipeline run finished for build: ${BUILD_NUMBER}"
}
success {
echo "Build ${BUILD_NUMBER} completed successfully!"
}
failure {
echo "Build ${BUILD_NUMBER} failed! Check console logs for errors."
}
}
Workspace Hygiene (cleanWs)
Over continuous executions, worker node storage risks depletion from untracked dependencies, leftover build files, and Docker contexts. The cleanWs() step cleans the agent workspace.
stage('Cleanup') {
steps {
cleanWs()
}
}
Critical Execution Order: Always run
archiveArtifactsbefore executingcleanWs(). IfcleanWs()executes first, all generated artifacts are deleted from the disk, causingarchiveArtifactsto fail withNo artifacts found that match the file pattern.
9. Real-World Debugging and Practical Pitfalls
During pipeline construction, several common production issues can emerge:
Issue 1: Missing Tooling in Execution Agent (docker: not found)
- Symptom: Pipeline stages invoking
sh 'docker ...'fail immediately withdocker: command not found. - Cause: The Jenkins agent or containerized controller lacks the Docker CLI client binary or lacks access to the host's
/var/run/docker.sock. - Resolution: Install the Docker CLI inside the execution container (as
root) and ensure the Jenkins runtime user belongs to thedockerusergroup:BASHdocker exec -u root -it jenkins bash apt-get update && apt-get install -y docker.io usermod -aG docker jenkins
Issue 2: Groovy String Interpolation (Single vs. Double Quotes)
- Symptom:
archiveArtifacts artifacts: 'deploy-info-${BUILD_NUMBER}.txt'throwsNo artifacts found matching pattern deploy-info-${BUILD_NUMBER}.txt. - Root Cause: In Groovy, single-quoted strings (
'...') are string literals; no variable interpolation occurs. Double-quoted strings ("...") are GStrings, which evaluate${BUILD_NUMBER}dynamically. - Resolution: Use double quotes whenever reading Jenkins environment variables:
GROOVY
// WRONG: Jenkins searches for literal string '${BUILD_NUMBER}' archiveArtifacts artifacts: 'deploy-info-${BUILD_NUMBER}.txt' // CORRECT: Jenkins evaluates to 'deploy-info-11.txt' archiveArtifacts artifacts: "deploy-info-${BUILD_NUMBER}.txt"
Issue 3: Curly Brace Scoping Mismatches
- Symptom: Jenkins displays syntactical compile errors such as
WorkflowScript: 45: Method definition not allowed hereorunexpected token: stage. - Root Cause: Appending stages outside the closing
stages { ... }block or nesting steps directly insidestageswithout a parentstageblock. - Resolution: Maintain strict indentation checks. Remember that all
stageitems belong exclusively insidestages { ... }, and pipeline-levelposthooks belong directly insidepipeline { ... }.
Issue 4: Failing Defensive Cleanup Commands
- Symptom: Pipelines fail during deployment because
docker rm -f <container>returns exit status 1 if no existing container exists. - Resolution: Append defensive bash or logic fallbacks:
GROOVY
sh 'docker stop flask-app || true' sh 'docker rm flask-app || true'
10. The Complete Production-Grade Jenkinsfile
Below is the fully refactored, production-ready Jenkinsfile designed to be committed to the root of your Git repository and executed via Pipeline script from SCM.
11. Complete Pipeline Execution Flow
12. Summary and Key Takeaways
- Configuration as Code Over UI: Migrating from Freestyle jobs to declarative
Jenkinsfiledefinitions allows CI/CD logic to be version-controlled, code-reviewed, and tracked alongside application code. - Structural Integrity: The Declarative syntax tree strictly enforces
pipeline -> agent -> stages -> stage -> steps. - String Interpolation Rules: Always use double quotes (
"...") when referencing pipeline environment variables (e.g.,deploy-info-${BUILD_NUMBER}.txt). Single quotes ('...') treat input as raw string literals. - Secure Secret Injection: Use the
withCredentialsblock to mask sensitive tokens and credentials from pipeline console outputs. - Idempotent Deployments: Utilize fallback guards (e.g.,
docker rm -f ... || true) to prevent deployment steps from failing when intermediate components do not exist. - Traceability Through Artifacts: Use
archiveArtifactswithfingerprint: trueto persist build-identifying metadata (git commit, image tags, timestamps) for downstream release audits. - Execution Order Matters: Ensure
archiveArtifactsis invoked beforecleanWs(); otherwise, files will be purged from the agent disk before Jenkins can archive them. - Workspace Hygiene: Implement automated post-build cleanup (
cleanWs()) to prevent uncontrolled disk storage consumption on Jenkins build nodes.