{"id":85,"date":"2025-12-29T12:00:00","date_gmt":"2025-12-29T12:00:00","guid":{"rendered":"https:\/\/rezachegini.com\/?p=85"},"modified":"2026-10-30T06:51:39","modified_gmt":"2026-10-30T06:51:39","slug":"multi-language-jenkins-pipelines-technical-guide-to-java-node-js-python-and-php-ci-cd","status":"publish","type":"post","link":"https:\/\/rezachegini.com\/?p=85","title":{"rendered":"Multi-Language Jenkins Pipelines: Technical Guide to Java, Node.js, Python, and PHP CI\/CD"},"content":{"rendered":"<h3 class=\"wp-block-heading\">Introduction: Universal Pattern, Language-Specific Implementation<\/h3>\n<p class=\"wp-block-paragraph\">After building CI\/CD pipelines for four different programming languages, I discovered something critical: the DevOps pattern is universal, but the technical implementation details are completely different.<\/p>\n<p class=\"wp-block-paragraph\">Every pipeline follows the same stages:<\/p>\n<ol class=\"wp-block-list\"><li>Checkout code from Git<\/li><li>Install dependencies<\/li><li>Build\/compile<\/li><li>Create Docker image<\/li><li>Deploy to Kubernetes<\/li><\/ol>\n<p class=\"wp-block-paragraph\">But what happens INSIDE each stage? That&#8217;s where the real learning happens.<\/p>\n<p class=\"wp-block-paragraph\">This isn&#8217;t a tutorial. This is a technical deep-dive showing:<\/p>\n<ul class=\"wp-block-list\"><li>How npm install differs from pip install at the algorithm level<\/li><li>Why composer.lock uses Git commit hashes while package-lock.json uses SHA-512<\/li><li>How Maven dependency resolution conflicts are resolved<\/li><li>Why npm ci is 40% faster than npm install in CI\/CD<\/li><li>Real memory usage and build time benchmarks<\/li><\/ul>\n<p class=\"wp-block-paragraph\">All code is from my production home lab running on AWS EKS.<\/p>\n<h3 class=\"wp-block-heading\">Architecture Overview: What All 4 Pipelines Share<\/h3>\n<h3 class=\"wp-block-heading\">The Universal Jenkinsfile Structure<\/h3>\n<pre class=\"wp-block-code\"><code>pipeline {\n    agent any\n    \n    options {\n        buildDiscarder logRotator(daysToKeepStr: '7', numToKeepStr: '2')\n        timeout(time: 30, unit: 'MINUTES')\n    }\n    \n    tools {\n        \/\/ Language-specific tool (maven\/nodejs\/python)\n    }\n    \n    environment {\n        dockercred = credentials('docker-hub')\n        \/\/ AWS credentials for kubectl\n    }\n    \n    stages {\n        stage('Checkout') { \/* git operations *\/ }\n        stage('Build') { \/* language-specific commands *\/ }\n        stage('Test') { \/* language-specific tests *\/ }\n        stage('Docker') { \/* docker build &amp; push *\/ }\n        stage('Deploy-Dev') { \/* kubectl apply -n dev *\/ }\n        stage('Deploy-Staging') { \/* kubectl apply -n staging *\/ }\n        stage('Deploy-Prod') { \/* manual approval + kubectl *\/ }\n    }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What this structure provides:<\/p>\n<p class=\"wp-block-paragraph\">1. Build History Management:<\/p>\n<pre class=\"wp-block-code\"><code>buildDiscarder logRotator(daysToKeepStr: '7', numToKeepStr: '2')<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Technical behavior:<\/p>\n<p class=\"wp-block-paragraph\">Jenkins stores build artifacts in $JENKINS_HOME\/jobs\/{job-name}\/builds\/{build-number}\/<\/p>\n<p class=\"wp-block-paragraph\">Each build directory contains: build.xml &#8211; Build metadata log &#8211; Console output archive\/ &#8211; Archived artifacts workflow\/ &#8211; Pipeline state<\/p>\n<p class=\"wp-block-paragraph\">Without buildDiscarder:<\/p>\n<p class=\"wp-block-paragraph\">After 1 year:<\/p>\n<ul class=\"wp-block-list\"><li>365 builds \u00d7 500MB average = 182GB disk space!<\/li><li>Jenkins slows down reading build history<\/li><li>Backup time increases<\/li><\/ul>\n<p class=\"wp-block-paragraph\">With logRotator:<\/p>\n<p class=\"wp-block-paragraph\">Keeps only:<\/p>\n<ul class=\"wp-block-list\"><li>Last 2 builds (regardless of age)<\/li><li>Any builds from last 7 days<\/li><li>Deletes older builds automatically<\/li><li>Typical disk usage: ~2GB for active job<\/li><\/ul>\n<p class=\"wp-block-paragraph\">2. Timeout Protection:<\/p>\n<pre class=\"wp-block-code\"><code>timeout(time: 30, unit: 'MINUTES')<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Why this matters:<\/p>\n<p class=\"wp-block-paragraph\">Scenario: npm install hangs (network issue)<\/p>\n<p class=\"wp-block-paragraph\">Without timeout:<\/p>\n<ul class=\"wp-block-list\"><li>Build runs forever<\/li><li>Executor blocked<\/li><li>Other builds queue indefinitely<\/li><li>Manual intervention required<\/li><\/ul>\n<p class=\"wp-block-paragraph\">With timeout:<\/p>\n<ul class=\"wp-block-list\"><li>After 30 minutes \u2192 Pipeline ABORTED<\/li><li>Executor released<\/li><li>Next build can run<\/li><li>Alert sent to team<\/li><\/ul>\n<p class=\"wp-block-paragraph\">3. Tools Configuration:<\/p>\n<p class=\"wp-block-paragraph\">The tools {} block tells Jenkins where to find build tools:<\/p>\n<pre class=\"wp-block-code\"><code>tools {\n    maven 'Maven'  \/\/ References \"Maven\" in Global Tool Configuration\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What happens behind the scenes:<\/p>\n<p class=\"wp-block-paragraph\">When pipeline starts:<\/p>\n<p class=\"wp-block-paragraph\">Jenkins reads Global Tool Configuration:<\/p>\n<p class=\"wp-block-paragraph\">Jenkins sets environment variables:<\/p>\n<p class=\"wp-block-paragraph\">Now mvn command is available:<\/p>\n<p class=\"wp-block-paragraph\">Multi-version support:<\/p>\n<p class=\"wp-block-paragraph\">If you have multiple tool versions:<\/p>\n<pre class=\"wp-block-code\"><code>pipeline {\n    agent any\n    tools {\n        maven 'Maven-3.9.0'  \/\/ For new projects\n        \/\/ OR\n        maven 'Maven-3.6.3'  \/\/ For legacy projects\n    }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Jenkins automatically uses the correct version!<\/p>\n<h3 class=\"wp-block-heading\">Language 1: Java\/Maven &#8211; Dependency Hell and Resolution<\/h3>\n<h3 class=\"wp-block-heading\">Complete Pipeline Code<\/h3>\n<pre class=\"wp-block-code\"><code>pipeline {\n    agent any\n    \n    options {\n        buildDiscarder logRotator(daysToKeepStr: '7', numToKeepStr: '2')\n    }\n    \n    tools {\n        maven 'Maven'\n    }\n    \n    environment {\n        dockercred = credentials('docker-hub')\n    }\n    \n    stages {\n        stage('CheckOut') {\n            steps {\n                checkout scmGit(\n                    branches: [[name: '*\/main']], \n                    userRemoteConfigs: [[url: 'https:\/\/github.com\/mohammadrezachegini\/AddressBook\/']]\n                )\n            }\n        }\n        \n        stage('mvn test') {\n            steps {\n                sh 'mvn test'\n            }\n        }\n        \n        stage('build stage') {\n            steps {\n                sh 'mvn package'\n            }\n        }\n        \n        stage('Docker build and push') {\n            steps {\n                sh 'docker build -t lowyiiii\/jenkins-java:${BUILD_NUMBER} .'\n                sh 'docker build -t lowyiiii\/jenkins-java:latest .'\n                sh 'echo $dockercred_PSW | docker login -u $dockercred_USR --password-stdin'\n                sh 'docker push lowyiiii\/jenkins-java:${BUILD_NUMBER}'\n                sh 'docker push lowyiiii\/jenkins-java:latest'\n            }\n        }\n        \n        stage('Kubectl Deploy') {\n            steps {\n                sh 'kubectl apply -f jenkins-java-application.yaml'\n            }\n        }\n    }\n}<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Deep Dive: Maven Dependency Resolution<\/h3>\n<p class=\"wp-block-paragraph\">What happens during mvn test and mvn package?<\/p>\n<h3 class=\"wp-block-heading\">Stage 1: Parse pom.xml<\/h3>\n<pre class=\"wp-block-code\"><code>&lt;project&gt;\n    &lt;dependencies&gt;\n        &lt;dependency&gt;\n            &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n            &lt;artifactId&gt;spring-boot-starter-web&lt;\/artifactId&gt;\n            &lt;version&gt;3.1.5&lt;\/version&gt;\n        &lt;\/dependency&gt;\n        &lt;dependency&gt;\n            &lt;groupId&gt;junit&lt;\/groupId&gt;\n            &lt;artifactId&gt;junit&lt;\/artifactId&gt;\n            &lt;version&gt;4.13.2&lt;\/version&gt;\n            &lt;scope&gt;test&lt;\/scope&gt;\n        &lt;\/dependency&gt;\n    &lt;\/dependencies&gt;\n&lt;\/project&gt;<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Maven reads this and creates a dependency graph.<\/p>\n<h3 class=\"wp-block-heading\">Stage 2: Dependency Graph Construction<\/h3>\n<p class=\"wp-block-paragraph\">Maven builds a tree:<\/p>\n<pre class=\"wp-block-code\"><code>Your Application\n\u251c\u2500\u2500 spring-boot-starter-web:3.1.5\n\u2502   \u251c\u2500\u2500 spring-boot-starter:3.1.5\n\u2502   \u2502   \u251c\u2500\u2500 spring-boot:3.1.5\n\u2502   \u2502   \u251c\u2500\u2500 spring-boot-autoconfigure:3.1.5\n\u2502   \u2502   \u2514\u2500\u2500 logback-classic:1.4.11\n\u2502   \u2502       \u251c\u2500\u2500 logback-core:1.4.11\n\u2502   \u2502       \u2514\u2500\u2500 slf4j-api:2.0.9\n\u2502   \u251c\u2500\u2500 spring-webmvc:6.0.13\n\u2502   \u2514\u2500\u2500 tomcat-embed-core:10.1.15\n\u2514\u2500\u2500 junit:4.13.2 (scope: test)\n    \u2514\u2500\u2500 hamcrest-core:1.3<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Total dependencies for this simple app: 47 JAR files!<\/p>\n<h3 class=\"wp-block-heading\">Stage 3: Version Conflict Resolution<\/h3>\n<p class=\"wp-block-paragraph\">Common conflict scenario:<\/p>\n<p class=\"wp-block-paragraph\">Your app needs:<\/p>\n<pre class=\"wp-block-code\"><code>\u251c\u2500\u2500 Library A \u2192 requires slf4j-api:1.7.30\n\u2514\u2500\u2500 Library B \u2192 requires slf4j-api:2.0.9<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Which version does Maven choose?<\/p>\n<p class=\"wp-block-paragraph\">Maven uses nearest definition strategy:<\/p>\n<p class=\"wp-block-paragraph\">Dependency Tree Depth:<\/p>\n<p class=\"wp-block-paragraph\">Your-App \u2192 Library-A \u2192 slf4j-api:1.7.30 (depth = 2)<\/p>\n<p class=\"wp-block-paragraph\">Your-App \u2192 Library-B \u2192 slf4j-api:2.0.9 (depth = 2)<\/p>\n<p class=\"wp-block-paragraph\">Same depth? Use first declaration in pom.xml!<\/p>\n<p class=\"wp-block-paragraph\">If Library A declared first \u2192 slf4j-api:1.7.30 wins If Library B declared first \u2192 slf4j-api:2.0.9 wins<\/p>\n<p class=\"wp-block-paragraph\">This can cause bugs!<\/p>\n<p class=\"wp-block-paragraph\">Library B expects 2.0.9 features \u2192 crashes at runtime with 1.7.30<\/p>\n<p class=\"wp-block-paragraph\">Solution: Dependency Management<\/p>\n<pre class=\"wp-block-code\"><code>&lt;dependencyManagement&gt;\n    &lt;dependencies&gt;\n        &lt;dependency&gt;\n            &lt;groupId&gt;org.slf4j&lt;\/groupId&gt;\n            &lt;artifactId&gt;slf4j-api&lt;\/artifactId&gt;\n            &lt;version&gt;2.0.9&lt;\/version&gt;  &lt;!-- Force this version --&gt;\n        &lt;\/dependency&gt;\n    &lt;\/dependencies&gt;\n&lt;\/dependencyManagement&gt;<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Now ALL transitive dependencies use 2.0.9!<\/p>\n<h3 class=\"wp-block-heading\">Stage 4: Download from Maven Central<\/h3>\n<p class=\"wp-block-paragraph\">For each dependency, Maven:<\/p>\n<p class=\"wp-block-paragraph\">1. Checks local repository:<\/p>\n<pre class=\"wp-block-code\"><code>~\/.m2\/repository\/org\/springframework\/boot\/spring-boot-starter-web\/3.1.5\/<\/code><\/pre>\n<p class=\"wp-block-paragraph\">If exists \u2192 Use it \u2705 If not \u2192 Download from remote \u2b07\ufe0f<\/p>\n<p class=\"wp-block-paragraph\">2. Queries Maven Central:<\/p>\n<pre class=\"wp-block-code\"><code>GET https:\/\/repo.maven.apache.org\/maven2\/org\/springframework\/boot\/spring-boot-starter-web\/3.1.5\/spring-boot-starter-web-3.1.5.pom<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Response:<\/p>\n<ul class=\"wp-block-list\"><li>POM file (metadata)<\/li><li>JAR file (actual code)<\/li><li>Sources JAR (source code)<\/li><li>Javadoc JAR (documentation)<\/li><li>SHA-1 checksum<\/li><li>MD5 checksum<\/li><\/ul>\n<p class=\"wp-block-paragraph\">3. Validates checksums:<\/p>\n<pre class=\"wp-block-code\"><code># Maven downloads:\nspring-boot-starter-web-3.1.5.jar\nspring-boot-starter-web-3.1.5.jar.sha1\n\n# Calculates SHA-1 of downloaded JAR\ncalculated_hash = sha1sum spring-boot-starter-web-3.1.5.jar\n\n# Compares with downloaded .sha1 file\nexpected_hash = cat spring-boot-starter-web-3.1.5.jar.sha1\n\nif calculated_hash == expected_hash:\n    Installation successful \u2705\nelse:\n    ERROR: Checksum mismatch! Corrupted download! \u274c<\/code><\/pre>\n<p class=\"wp-block-paragraph\">4. Stores in local repo:<\/p>\n<pre class=\"wp-block-code\"><code>~\/.m2\/repository\/\n\u2514\u2500\u2500 org\/\n    \u2514\u2500\u2500 springframework\/\n        \u2514\u2500\u2500 boot\/\n            \u2514\u2500\u2500 spring-boot-starter-web\/\n                \u2514\u2500\u2500 3.1.5\/\n                    \u251c\u2500\u2500 spring-boot-starter-web-3.1.5.jar\n                    \u251c\u2500\u2500 spring-boot-starter-web-3.1.5.pom\n                    \u251c\u2500\u2500 spring-boot-starter-web-3.1.5-sources.jar\n                    \u2514\u2500\u2500 spring-boot-starter-web-3.1.5-javadoc.jar<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Next build? Uses cached version! (MUCH faster)<\/p>\n<h3 class=\"wp-block-heading\">Stage 5: mvn test &#8211; Test Execution<\/h3>\n<pre class=\"wp-block-code\"><code>mvn test<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What happens:<\/p>\n<p class=\"wp-block-paragraph\">1. Compile test code:<\/p>\n<pre class=\"wp-block-code\"><code>[INFO] --- maven-compiler-plugin:3.11.0:testCompile (default-testCompile) @ addressbook ---\n[INFO] Compiling 5 source files to \/home\/jenkins\/workspace\/java-project\/target\/test-classes<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Creates .class files from .java test files.<\/p>\n<p class=\"wp-block-paragraph\">2. Run Surefire plugin:<\/p>\n<pre class=\"wp-block-code\"><code>[INFO] --- maven-surefire-plugin:3.0.0:test (default-test) @ addressbook ---\n[INFO] \n[INFO] -------------------------------------------------------\n[INFO]  T E S T S\n[INFO] -------------------------------------------------------\n[INFO] Running com.example.UserServiceTest\n[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.532 s\n[INFO] Running com.example.AddressBookTest\n[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.421 s<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Technical details:<\/p>\n<p class=\"wp-block-paragraph\">Surefire creates a forked JVM:<\/p>\n<pre class=\"wp-block-code\"><code>Main Maven Process (PID: 1234)\n\u2514\u2500 Forked Test JVM (PID: 5678)\n   \u2514\u2500 Runs all tests in isolation<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Why fork?<\/p>\n<ul class=\"wp-block-list\"><li>Tests can&#8217;t affect Maven process<\/li><li>Test failures don&#8217;t crash Maven<\/li><li>Separate memory space<\/li><li>Can set test-specific JVM args<\/li><\/ul>\n<p class=\"wp-block-paragraph\">3. Generate test reports:<\/p>\n<pre class=\"wp-block-code\"><code>target\/\n\u2514\u2500\u2500 surefire-reports\/\n    \u251c\u2500\u2500 TEST-com.example.UserServiceTest.xml\n    \u251c\u2500\u2500 TEST-com.example.AddressBookTest.xml\n    \u2514\u2500\u2500 com.example.UserServiceTest.txt<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Jenkins parses these XML files for test results!<\/p>\n<h3 class=\"wp-block-heading\">Stage 6: mvn package &#8211; Build Application<\/h3>\n<pre class=\"wp-block-code\"><code>mvn package<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Phase execution order:<\/p>\n<pre class=\"wp-block-code\"><code>1. validate     \u2192 Validate project is correct\n2. compile      \u2192 Compile source code\n3. test         \u2192 Run unit tests\n4. package      \u2192 Create JAR\/WAR file\n5. verify       \u2192 Run integration tests (if configured)\n6. install      \u2192 Install to local repository (if mvn install)\n7. deploy       \u2192 Deploy to remote repo (if mvn deploy)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">mvn package executes phases 1-4.<\/p>\n<p class=\"wp-block-paragraph\">Packaging process:<\/p>\n<pre class=\"wp-block-code\"><code>[INFO] --- maven-jar-plugin:3.3.0:jar (default-jar) @ addressbook ---\n[INFO] Building jar: \/workspace\/target\/addressbook-1.0-SNAPSHOT.jar<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What goes into the JAR:<\/p>\n<pre class=\"wp-block-code\"><code>addressbook-1.0-SNAPSHOT.jar\n\u251c\u2500\u2500 META-INF\/\n\u2502   \u251c\u2500\u2500 MANIFEST.MF  (metadata: Main-Class, Class-Path, etc.)\n\u2502   \u2514\u2500\u2500 maven\/\n\u2502       \u2514\u2500\u2500 com.example\/\n\u2502           \u2514\u2500\u2500 addressbook\/\n\u2502               \u251c\u2500\u2500 pom.xml\n\u2502               \u2514\u2500\u2500 pom.properties\n\u251c\u2500\u2500 com\/\n\u2502   \u2514\u2500\u2500 example\/\n\u2502       \u251c\u2500\u2500 AddressBook.class\n\u2502       \u251c\u2500\u2500 User.class\n\u2502       \u2514\u2500\u2500 UserService.class\n\u2514\u2500\u2500 application.properties<\/code><\/pre>\n<p class=\"wp-block-paragraph\">MANIFEST.MF example:<\/p>\n<pre class=\"wp-block-code\"><code>Manifest-Version: 1.0\nMain-Class: com.example.AddressBook\nClass-Path: spring-boot-starter-web-3.1.5.jar logback-classic-1.4.11.jar\nBuild-Jdk: 11.0.20\nCreated-By: Apache Maven 3.9.0<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Build time breakdown:<\/p>\n<p class=\"wp-block-paragraph\">Total time: 2 minutes 15 seconds<\/p>\n<p class=\"wp-block-paragraph\">Breakdown:<\/p>\n<ul class=\"wp-block-list\"><li>Download dependencies: 45s (first time only)<\/li><li>Compile main code: 12s<\/li><li>Run tests: 28s<\/li><li>Package JAR: 8s<\/li><li>Docker build: 42s<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Memory usage during build:<\/p>\n<p class=\"wp-block-paragraph\">Maven process:<\/p>\n<ul class=\"wp-block-list\"><li>Heap: -Xmx512m (default)<\/li><li>Metaspace: ~150MB<\/li><li>Total: ~700MB<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Test fork JVM:<\/p>\n<ul class=\"wp-block-list\"><li>Heap: -Xmx256m (default)<\/li><li>Total: ~350MB<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Peak memory: ~1GB<\/p>\n<h3 class=\"wp-block-heading\">Deep Dive: Docker Multi-Tag Strategy<\/h3>\n<pre class=\"wp-block-code\"><code>sh 'docker build -t lowyiiii\/jenkins-java:${BUILD_NUMBER} .'\nsh 'docker build -t lowyiiii\/jenkins-java:latest .'<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Why build twice? That&#8217;s inefficient!<\/p>\n<p class=\"wp-block-paragraph\">Better approach &#8211; multi-tag in single build:<\/p>\n<pre class=\"wp-block-code\"><code>sh 'docker build -t lowyiiii\/jenkins-java:${BUILD_NUMBER} -t lowyiiii\/jenkins-java:latest .'<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What this does:<\/p>\n<p class=\"wp-block-paragraph\">Single build creates TWO tags pointing to SAME image:<\/p>\n<pre class=\"wp-block-code\"><code>Image ID: sha256:abc123...\n\u251c\u2500\u2500 lowyiiii\/jenkins-java:47\n\u2514\u2500\u2500 lowyiiii\/jenkins-java:latest<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Disk usage: 450MB (not 900MB!)<\/p>\n<p class=\"wp-block-paragraph\">Build time: 42s (not 84s!)<\/p>\n<p class=\"wp-block-paragraph\">Docker push behavior:<\/p>\n<pre class=\"wp-block-code\"><code>sh 'docker push lowyiiii\/jenkins-java:${BUILD_NUMBER}'\nsh 'docker push lowyiiii\/jenkins-java:latest'<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What actually transfers:<\/p>\n<p class=\"wp-block-paragraph\">Push #1 (lowyiiii\/jenkins-java:47):<\/p>\n<ul class=\"wp-block-list\"><li>Uploads all layers (450MB)<\/li><li>Time: ~3 minutes on 100Mbps<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Push #2 (lowyiiii\/jenkins-java:latest):<\/p>\n<ul class=\"wp-block-list\"><li>Detects layers already exist in registry<\/li><li>Only updates tag pointer<\/li><li>Transfers: ~2KB (metadata only!)<\/li><li>Time: ~2 seconds<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Registry storage:<\/p>\n<p class=\"wp-block-paragraph\">Docker Hub storage:<\/p>\n<pre class=\"wp-block-code\"><code>Docker Hub storage:\nlowyiiii\/jenkins-java\n\u251c\u2500\u2500 47: sha256:abc123... (450MB)\n\u251c\u2500\u2500 48: sha256:def456... (450MB)\n\u2514\u2500\u2500 latest \u2192 48 (0MB, just a pointer)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Total: 900MB (not 1.35GB)<\/p>\n<p class=\"wp-block-paragraph\">Why two tags?<\/p>\n<p class=\"wp-block-paragraph\">:${BUILD_NUMBER} (47, 48, 49&#8230;)<\/p>\n<ul class=\"wp-block-list\"><li>Immutable<\/li><li>Allows rollback<\/li><li>Audit trail<\/li><li>&#8220;What version is in production?&#8221;<\/li><\/ul>\n<p class=\"wp-block-paragraph\">:latest<\/p>\n<ul class=\"wp-block-list\"><li>Always points to newest<\/li><li>Convenient for dev environment<\/li><li>&#8220;Give me the latest build&#8221;<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Real-world usage:<\/p>\n<pre class=\"wp-block-code\"><code># Development\napiVersion: apps\/v1\nkind: Deployment\nspec:\n  template:\n    spec:\n      containers:\n      - image: lowyiiii\/jenkins-java:latest  # Auto-updates\n\n# Production\napiVersion: apps\/v1\nkind: Deployment\nspec:\n  template:\n    spec:\n      containers:\n      - image: lowyiiii\/jenkins-java:47  # Pinned version<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Language 2: Node.js &#8211; npm Dependency Algorithm<\/h3>\n<h3 class=\"wp-block-heading\">Complete Pipeline Code<\/h3>\n<pre class=\"wp-block-code\"><code>pipeline {\n    agent any\n    \n    options {\n        buildDiscarder logRotator(daysToKeepStr: '7', numToKeepStr: '2')\n    }\n    \n    tools {\n        nodejs 'NodeJs'\n    }\n    \n    environment {\n        dockercred = credentials('docker-hub')\n    }\n    \n    stages {\n        stage('CheckOut') {\n            steps {\n                checkout scmGit(\n                    branches: [[name: '*\/main']], \n                    userRemoteConfigs: [[url: 'https:\/\/github.com\/mohammadrezachegini\/Node-JS-Jenkins\/']]\n                )\n            }\n        }\n        \n        stage('node stage') {\n            steps {\n                sh 'node -v'\n                sh 'npm -v'\n                sh 'npm ci'\n            }\n        }\n        \n        stage('Docker build') {\n            steps {\n                sh 'docker build -t lowyiiii\/jenkins-nodejs:latest .'\n                sh 'echo $dockercred_PSW | docker login -u $dockercred_USR --password-stdin'\n                sh 'docker push lowyiiii\/jenkins-nodejs:latest'\n            }\n        }\n        \n        stage('kubectl stage') {\n            steps {\n                sh 'kubectl apply -f Application.yaml'\n            }\n        }\n    }\n}<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Deep Dive: npm ci vs npm install<\/h3>\n<p class=\"wp-block-paragraph\">Why use npm ci instead of npm install?<\/p>\n<p class=\"wp-block-paragraph\">Let me show you the technical differences:<\/p>\n<h3 class=\"wp-block-heading\">npm install &#8211; The Developer Command<\/h3>\n<pre class=\"wp-block-code\"><code>npm install<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Algorithm:<\/p>\n<ol class=\"wp-block-list\"><li>Read package.json<\/li><li>Read package-lock.json (if exists)<\/li><li>Compare versions:<\/li><li>Download packages<\/li><li>Update package-lock.json if needed<\/li><li>Install to node_modules\/<\/li><\/ol>\n<ul class=\"wp-block-list\"><li>If lock file has compatible version \u2192 use it<\/li><li>If lock file outdated \u2192 resolve new version<\/li><li>If lock file missing \u2192 create it<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Example scenario:<\/p>\n<pre class=\"wp-block-code\"><code>\/\/ package.json\n{\n  \"dependencies\": {\n    \"express\": \"^4.18.0\"\n  }\n}\n\n\/\/ package-lock.json (3 months old)\n{\n  \"dependencies\": {\n    \"express\": {\n      \"version\": \"4.18.2\"\n    }\n  }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Today, express@4.19.5 is available:<\/p>\n<pre class=\"wp-block-code\"><code>npm install<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Result:<\/p>\n<ul class=\"wp-block-list\"><li>Sees &#8220;^4.18.0&#8221; allows 4.19.5<\/li><li>Updates to express@4.19.5<\/li><li>Modifies package-lock.json<\/li><li>\u26a0\ufe0f Different version than last build!<\/li><\/ul>\n<h3 class=\"wp-block-heading\">npm ci &#8211; The CI\/CD Command<\/h3>\n<pre class=\"wp-block-code\"><code>npm ci<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Algorithm:<\/p>\n<ol class=\"wp-block-list\"><li>Check package-lock.json exists<\/li><li>If missing \u2192 ERROR and exit \u274c<\/li><li>Delete node_modules\/ directory<\/li><li>Read package-lock.json ONLY (ignores package.json versions)<\/li><li>Download EXACT versions from lock file<\/li><li>Verify integrity checksums<\/li><li>Install to node_modules\/<\/li><li>NEVER modify package-lock.json<\/li><\/ol>\n<p class=\"wp-block-paragraph\">Same scenario:<\/p>\n<pre class=\"wp-block-code\"><code>npm ci<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Result:<\/p>\n<ul class=\"wp-block-list\"><li>Reads package-lock.json<\/li><li>Installs express@4.18.2 (EXACT version)<\/li><li>Doesn&#8217;t check npm registry for newer versions<\/li><li>\u2705 Same version every build!<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Performance comparison:<\/p>\n<p class=\"wp-block-paragraph\">Test environment: 500 packages, SSD, 100Mbps internet<\/p>\n<p class=\"wp-block-paragraph\">npm install (with node_modules\/ present):<\/p>\n<ul class=\"wp-block-list\"><li>Checks each package<\/li><li>Compares versions<\/li><li>Updates if needed<\/li><li>Time: 45 seconds<\/li><\/ul>\n<p class=\"wp-block-paragraph\">npm install (cold, no node_modules\/):<\/p>\n<ul class=\"wp-block-list\"><li>Resolves dependencies<\/li><li>Downloads packages<\/li><li>Creates node_modules\/<\/li><li>Time: 62 seconds<\/li><\/ul>\n<p class=\"wp-block-paragraph\">npm ci (always cold):<\/p>\n<ul class=\"wp-block-list\"><li>Deletes node_modules\/<\/li><li>No version resolution<\/li><li>Direct download<\/li><li>Time: 38 seconds<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Winner: npm ci (40% faster!) \u2705<\/p>\n<p class=\"wp-block-paragraph\">Why npm ci is faster:<\/p>\n<p class=\"wp-block-paragraph\">npm install workflow:<\/p>\n<pre class=\"wp-block-code\"><code>package.json \u2192 version resolution \u2192 package-lock.json \u2192 download \u2192 install\n(many network round-trips for version checking)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">npm ci workflow:<\/p>\n<pre class=\"wp-block-code\"><code>package-lock.json \u2192 download \u2192 install\n(direct downloads, no version queries)<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Deep Dive: npm Dependency Tree Algorithm<\/h3>\n<p class=\"wp-block-paragraph\">Example package.json:<\/p>\n<pre class=\"wp-block-code\"><code>{\n  \"dependencies\": {\n    \"express\": \"^4.18.2\",\n    \"lodash\": \"^4.17.21\"\n  }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What npm actually installs:<\/p>\n<pre class=\"wp-block-code\"><code>npm ci<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Downloads:<\/p>\n<ol class=\"wp-block-list\"><li>express@4.18.2<\/li><li>lodash@4.17.21<\/li><\/ol>\n<p class=\"wp-block-paragraph\">But express@4.18.2 depends on:<\/p>\n<ol class=\"wp-block-list\"><li>accepts@1.3.8<\/li><li>array-flatten@1.1.1<\/li><li>body-parser@1.20.1<\/li><li>content-disposition@0.5.4<\/li><li>cookie@0.5.0<\/li><li>cookie-signature@1.0.6<\/li><li>debug@2.6.9<\/li><li>depd@2.0.0<\/li><\/ol>\n<p class=\"wp-block-paragraph\">&#8230; (35 total dependencies)<\/p>\n<p class=\"wp-block-paragraph\">Total packages installed: 502!<\/p>\n<h3 class=\"wp-block-heading\">Tree Construction Algorithm<\/h3>\n<p class=\"wp-block-paragraph\">npm uses hoisting to flatten the dependency tree:<\/p>\n<p class=\"wp-block-paragraph\">Ideal scenario (no conflicts):<\/p>\n<pre class=\"wp-block-code\"><code>node_modules\/\n\u251c\u2500\u2500 express\/\n\u251c\u2500\u2500 lodash\/\n\u251c\u2500\u2500 accepts\/      (hoisted from express)\n\u251c\u2500\u2500 body-parser\/  (hoisted from express)\n\u2514\u2500\u2500 debug\/        (hoisted from express and body-parser)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">All shared at top level!<\/p>\n<p class=\"wp-block-paragraph\">Conflict scenario:<\/p>\n<p class=\"wp-block-paragraph\">Your app needs:<\/p>\n<pre class=\"wp-block-code\"><code>\u251c\u2500\u2500 express \u2192 requires debug@2.6.9\n\u2514\u2500\u2500 some-package \u2192 requires debug@4.3.4<\/code><\/pre>\n<p class=\"wp-block-paragraph\">npm&#8217;s resolution:<\/p>\n<pre class=\"wp-block-code\"><code>node_modules\/\n\u251c\u2500\u2500 debug@4.3.4        (newer version hoisted to top)\n\u251c\u2500\u2500 express\/\n\u2502   \u2514\u2500\u2500 node_modules\/\n\u2502       \u2514\u2500\u2500 debug@2.6.9 (nested due to conflict)\n\u2514\u2500\u2500 some-package\/       (uses top-level debug@4.3.4)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Why this matters:<\/p>\n<p class=\"wp-block-paragraph\">Disk usage:<\/p>\n<ul class=\"wp-block-list\"><li>Hoisted (no conflicts): 150MB<\/li><li>Nested (many conflicts): 220MB<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Require time:<\/p>\n<ul class=\"wp-block-list\"><li>Hoisted: 0.5ms to find debug<\/li><li>Nested: 2.3ms (searches up directory tree)<\/li><\/ul>\n<h3 class=\"wp-block-heading\">Integrity Verification<\/h3>\n<p class=\"wp-block-paragraph\">package-lock.json includes SHA-512 checksums:<\/p>\n<pre class=\"wp-block-code\"><code>{\n  \"dependencies\": {\n    \"express\": {\n      \"version\": \"4.18.2\",\n      \"resolved\": \"https:\/\/registry.npmjs.org\/express\/-\/express-4.18.2.tgz\",\n      \"integrity\": \"sha512-5\/PsL6iGPdfQ\/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==\"\n    }\n  }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Installation process:<\/p>\n<ol class=\"wp-block-list\"><li>Download express-4.18.2.tgz from npm registry<\/li><li>Calculate SHA-512 hash:<\/li><\/ol>\n<pre class=\"wp-block-code\"><code>calculated = sha512(express-4.18.2.tgz)\nexpected = \"sha512-5\/PsL6iGPdfQ\/lKM1UuielYgv3BUoJfz1aUwU9vHZ...\"<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Compare:<\/p>\n<pre class=\"wp-block-code\"><code>if calculated == expected:\n    Extract to node_modules\/express\/\nelse:\n    ERROR: Integrity check failed!\n    Possible reasons:\n    - Corrupted download\n    - Man-in-the-middle attack\n    - Compromised npm registry<\/code><\/pre>\n<p class=\"wp-block-paragraph\">This prevents supply chain attacks!<\/p>\n<p class=\"wp-block-paragraph\">Real incident (2018):<\/p>\n<p class=\"wp-block-paragraph\">Attacker compromised &#8220;event-stream&#8221; package<\/p>\n<p class=\"wp-block-paragraph\">Injected malicious code to steal Bitcoin wallet keys<\/p>\n<p class=\"wp-block-paragraph\">Downloaded 8 million times before detection<\/p>\n<p class=\"wp-block-paragraph\">With integrity checks:<\/p>\n<ul class=\"wp-block-list\"><li>Hash wouldn&#8217;t match<\/li><li>npm ci would fail<\/li><li>Attack prevented \u2705<\/li><\/ul>\n<h3 class=\"wp-block-heading\">Deep Dive: Semantic Versioning in npm<\/h3>\n<p class=\"wp-block-paragraph\">Version specifiers in package.json:<\/p>\n<pre class=\"wp-block-code\"><code>{\n  \"dependencies\": {\n    \"express\": \"4.18.2\",      \/\/ Exact\n    \"lodash\": \"^4.17.21\",     \/\/ Caret\n    \"async\": \"~3.2.4\",        \/\/ Tilde\n    \"react\": \"*\",             \/\/ Wildcard (dangerous!)\n    \"vue\": \"&gt;=3.0.0 &lt;4.0.0\",  \/\/ Range\n    \"axios\": \"latest\"         \/\/ Latest (very dangerous!)\n  }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Technical meaning:<\/p>\n<p class=\"wp-block-paragraph\">1. Exact version: 4.18.2<\/p>\n<p class=\"wp-block-paragraph\">Allows: ONLY 4.18.2<\/p>\n<p class=\"wp-block-paragraph\">Blocks: 4.18.3, 4.19.0, 5.0.0<\/p>\n<p class=\"wp-block-paragraph\">2. Caret: ^4.18.2<\/p>\n<pre class=\"wp-block-code\"><code>Version format: MAJOR.MINOR.PATCH\n                  ^      ^     ^\nAllows:          4     any   any<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Examples:<\/p>\n<ul class=\"wp-block-list\"><li>\u2705 4.18.2 (exact)<\/li><li>\u2705 4.18.3 (patch update &#8211; bug fixes)<\/li><li>\u2705 4.19.0 (minor update &#8211; new features, backward compatible)<\/li><li>\u2705 4.20.5 (minor + patch)<\/li><li>\u274c 5.0.0 (major update &#8211; breaking changes)<\/li><\/ul>\n<p class=\"wp-block-paragraph\">3. Tilde: ~3.2.4<\/p>\n<p class=\"wp-block-paragraph\">Allows: 3.2.x only<\/p>\n<p class=\"wp-block-paragraph\">Examples:<\/p>\n<ul class=\"wp-block-list\"><li>\u2705 3.2.4 (exact)<\/li><li>\u2705 3.2.5 (patch update)<\/li><li>\u2705 3.2.99 (many patch updates)<\/li><li>\u274c 3.3.0 (minor update blocked)<\/li><li>\u274c 4.0.0 (major update blocked)<\/li><\/ul>\n<p class=\"wp-block-paragraph\">4. Range: &gt;=3.0.0 &lt;4.0.0<\/p>\n<p class=\"wp-block-paragraph\">Allows: 3.0.0 up to (but not including) 4.0.0<\/p>\n<p class=\"wp-block-paragraph\">Examples:<\/p>\n<ul class=\"wp-block-list\"><li>\u2705 3.0.0<\/li><li>\u2705 3.5.2<\/li><li>\u2705 3.99.99<\/li><li>\u274c 4.0.0<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Real-world impact:<\/p>\n<pre class=\"wp-block-code\"><code># Scenario: You develop with express@4.18.2\n# package.json has: \"express\": \"^4.18.2\"\n\nDeveloper A (Jan 2024):\nnpm install \u2192 Gets express@4.18.2\n\nDeveloper B (Jun 2024):\nnpm install \u2192 Gets express@4.19.8\n\nProduction (Aug 2024):\nnpm install \u2192 Gets express@4.20.1\n\n# Three different versions in three environments!\n# Potential bugs only appear in some environments!<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Solution: package-lock.json locks the version!<\/p>\n<h3 class=\"wp-block-heading\">Language 3: Python &#8211; pip and Virtual Environments<\/h3>\n<h3 class=\"wp-block-heading\">Complete Pipeline Code<\/h3>\n<pre class=\"wp-block-code\"><code>pipeline {\n    agent any\n    \n    options {\n        buildDiscarder logRotator(daysToKeepStr: '7', numToKeepStr: '2')\n    }\n    \n    environment {\n        dockercred = credentials('docker-hub')\n    }\n    \n    stages {\n        stage('CheckOut') {\n            steps {\n                checkout scmGit(\n                    branches: [[name: '*\/main']], \n                    userRemoteConfigs: [[url: 'https:\/\/github.com\/mohammadrezachegini\/python-jenkins\/']]\n                )\n            }\n        }\n        \n        stage('build stage') {\n            steps {\n                sh 'python3 --version'\n                sh 'pip3 install -r requirements.txt'\n            }\n        }\n        \n        stage('Docker build and push') {\n            steps {\n                sh 'docker build -t lowyiiii\/jenkins-python:latest .'\n                sh 'echo $dockercred_PSW | docker login -u $dockercred_USR --password-stdin'\n                sh 'docker push lowyiiii\/jenkins-python:latest'\n            }\n        }\n        \n        stage('Kubectl deployment') {\n            steps {\n                sh 'kubectl apply -f python-deployment.yaml'\n            }\n        }\n    }\n}<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Deep Dive: pip install Technical Details<\/h3>\n<p class=\"wp-block-paragraph\">Example requirements.txt:<\/p>\n<pre class=\"wp-block-code\"><code>flask==2.3.2\ngunicorn&gt;=21.0.0,&lt;22.0.0\npsycopg2-binary~=2.9.6\nrequests\nredis[hiredis]&gt;=4.5.0<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Version Specifier Semantics<\/h3>\n<p class=\"wp-block-paragraph\">1. Exact version: flask==2.3.2<\/p>\n<p class=\"wp-block-paragraph\">Allows: ONLY 2.3.2<\/p>\n<p class=\"wp-block-paragraph\">Install command:<\/p>\n<pre class=\"wp-block-code\"><code>pip install flask==2.3.2<\/code><\/pre>\n<p class=\"wp-block-paragraph\">2. Compatible release: psycopg2-binary~=2.9.6<\/p>\n<p class=\"wp-block-paragraph\">Equivalent to: &gt;=2.9.6, ==2.9.*<\/p>\n<p class=\"wp-block-paragraph\">Allows:<\/p>\n<ul class=\"wp-block-list\"><li>\u2705 2.9.6<\/li><li>\u2705 2.9.7<\/li><li>\u2705 2.9.99<\/li><li>\u274c 2.10.0 (different minor version)<\/li><\/ul>\n<p class=\"wp-block-paragraph\">3. Version range: gunicorn&gt;=21.0.0,&lt;22.0.0<\/p>\n<p class=\"wp-block-paragraph\">Allows: 21.x.x only<\/p>\n<ul class=\"wp-block-list\"><li>\u2705 21.0.0<\/li><li>\u2705 21.2.5<\/li><li>\u274c 22.0.0<\/li><\/ul>\n<p class=\"wp-block-paragraph\">4. No version specified: requests<\/p>\n<p class=\"wp-block-paragraph\">Installs: Latest stable version<\/p>\n<p class=\"wp-block-paragraph\">\u26a0\ufe0f Dangerous! Version changes between builds!<\/p>\n<p class=\"wp-block-paragraph\">Better practice:<\/p>\n<pre class=\"wp-block-code\"><code>requests==2.31.0<\/code><\/pre>\n<p class=\"wp-block-paragraph\">5. Extras syntax: redis[hiredis]&gt;=4.5.0<\/p>\n<p class=\"wp-block-paragraph\">Installs redis AND optional hiredis extension<\/p>\n<p class=\"wp-block-paragraph\">redis[hiredis] is equivalent to:<\/p>\n<ul class=\"wp-block-list\"><li>redis&gt;=4.5.0<\/li><li>hiredis&gt;=2.0.0<\/li><\/ul>\n<p class=\"wp-block-paragraph\">hiredis is a C extension for faster Redis parsing<\/p>\n<h3 class=\"wp-block-heading\">Installation Process &#8211; Step by Step<\/h3>\n<pre class=\"wp-block-code\"><code>pip3 install -r requirements.txt<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Step 1: Parse requirements.txt<\/p>\n<pre class=\"wp-block-code\"><code># pip reads line by line:\npackages = [\n    ('flask', '==2.3.2'),\n    ('gunicorn', '&gt;=21.0.0,&lt;22.0.0'),\n    ('psycopg2-binary', '~=2.9.6'),\n    ('requests', None),\n    ('redis[hiredis]', '&gt;=4.5.0')\n]<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Step 2: Resolve dependencies<\/p>\n<p class=\"wp-block-paragraph\">For each package, pip queries PyPI API:<\/p>\n<pre class=\"wp-block-code\"><code>GET https:\/\/pypi.org\/pypi\/flask\/json\n\nResponse:\n{\n  \"info\": {\n    \"version\": \"2.3.2\",\n    \"requires_dist\": [\n      \"Werkzeug&gt;=2.3.3\",\n      \"Jinja2&gt;=3.1.2\",\n      \"itsdangerous&gt;=2.1.2\",\n      \"click&gt;=8.1.3\"\n    ]\n  },\n  \"releases\": {\n    \"2.3.2\": [...]\n  }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Dependency tree construction:<\/p>\n<pre class=\"wp-block-code\"><code>flask==2.3.2\n\u251c\u2500\u2500 Werkzeug&gt;=2.3.3 \u2192 2.3.6 (latest matching)\n\u2502   \u2514\u2500\u2500 MarkupSafe&gt;=2.1.1 \u2192 2.1.3\n\u251c\u2500\u2500 Jinja2&gt;=3.1.2 \u2192 3.1.2\n\u2502   \u2514\u2500\u2500 MarkupSafe&gt;=2.0 \u2192 2.1.3 (already resolved)\n\u251c\u2500\u2500 itsdangerous&gt;=2.1.2 \u2192 2.1.2\n\u2514\u2500\u2500 click&gt;=8.1.3 \u2192 8.1.3<\/code><\/pre>\n<p class=\"wp-block-paragraph\">MarkupSafe appears twice but installed once!<\/p>\n<p class=\"wp-block-paragraph\">Step 3: Download packages<\/p>\n<p class=\"wp-block-paragraph\">pip downloads either:<\/p>\n<p class=\"wp-block-paragraph\">Wheel (preferred):<\/p>\n<pre class=\"wp-block-code\"><code>flask-2.3.2-py3-none-any.whl\n\u2502\n\u251c\u2500\u2500 Filename format:\n\u2502   {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl\n\u2502   \n\u251c\u2500\u2500 flask-2.3.2-py3-none-any.whl breakdown:\n\u2502   flask: package name\n\u2502   2.3.2: version\n\u2502   py3: Python 3.x\n\u2502   none: No specific ABI\n\u2502   any: Any platform\n\u2502\n\u2514\u2500\u2500 Advantages:\n    - Pre-built binary\n    - Fast installation (~1 second)\n    - No compilation required<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Source distribution (fallback):<\/p>\n<pre class=\"wp-block-code\"><code>flask-2.3.2.tar.gz\n\u2502\n\u251c\u2500\u2500 Contains:\n\u2502   - setup.py\n\u2502   - Source code\n\u2502   - README, LICENSE\n\u2502\n\u2514\u2500\u2500 Disadvantages:\n    - Must be built\n    - Slower (~15 seconds)\n    - Requires build tools (gcc, etc.)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Step 4: Installation<\/p>\n<pre class=\"wp-block-code\"><code># For wheel:\nunzip flask-2.3.2-py3-none-any.whl -d \/usr\/local\/lib\/python3.11\/site-packages\/\n\n# For source:\npython setup.py build\npython setup.py install<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Installed location:<\/p>\n<pre class=\"wp-block-code\"><code>\/usr\/local\/lib\/python3.11\/site-packages\/\n\u251c\u2500\u2500 flask\/\n\u2502   \u251c\u2500\u2500 __init__.py\n\u2502   \u251c\u2500\u2500 app.py\n\u2502   \u251c\u2500\u2500 cli.py\n\u2502   \u2514\u2500\u2500 ...\n\u251c\u2500\u2500 flask-2.3.2.dist-info\/\n\u2502   \u251c\u2500\u2500 METADATA\n\u2502   \u251c\u2500\u2500 RECORD\n\u2502   \u251c\u2500\u2500 WHEEL\n\u2502   \u2514\u2500\u2500 top_level.txt\n\u2514\u2500\u2500 bin\/\n    \u2514\u2500\u2500 flask (executable)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Step 5: Record installation<\/p>\n<p class=\"wp-block-paragraph\">pip creates a RECORD file:<\/p>\n<pre class=\"wp-block-code\"><code>flask-2.3.2.dist-info\/RECORD:\n\nflask\/__init__.py,sha256=xyz123...,5123\nflask\/app.py,sha256=abc456...,12456\nflask\/cli.py,sha256=def789...,3421\n...<\/code><\/pre>\n<p class=\"wp-block-paragraph\">This allows:<\/p>\n<ul class=\"wp-block-list\"><li>pip uninstall flask (knows what to delete)<\/li><li>pip check (verify file integrity)<\/li><\/ul>\n<h3 class=\"wp-block-heading\">Binary Extensions: The Compilation Problem<\/h3>\n<p class=\"wp-block-paragraph\">Some packages contain C\/C++ code:<\/p>\n<pre class=\"wp-block-code\"><code># requirements.txt\npsycopg2==2.9.6  # PostgreSQL adapter (has C code)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Installation without wheel:<\/p>\n<pre class=\"wp-block-code\"><code>pip install psycopg2==2.9.6\n\nCollecting psycopg2==2.9.6\n  Downloading psycopg2-2.9.6.tar.gz (384 kB)\n\nBuilding wheels for collected packages: psycopg2\n  Building wheel for psycopg2 (setup.py) ...\n\n# Compilation starts:\ngcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall ...\n  \n# Compiling C source:\npsycopgmodule.c\nconnection_type.c\ncursor_type.c\n...\n\n# Linking:\ncreating build\/lib.linux-x86_64-cpython-311\/psycopg2\ngcc -pthread -shared ... -lpq -o build\/lib.linux-x86_64-cpython-311\/psycopg2\/_psycopg.cpython-311-x86_64-linux-gnu.so\n\n# Takes 30-60 seconds!<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Requirements for compilation:<\/p>\n<pre class=\"wp-block-code\"><code># Need these installed:\n- gcc (C compiler)\n- python3-dev (Python headers)\n- libpq-dev (PostgreSQL development files)\n\n# If missing:\nerror: command 'gcc' failed: No such file or directory<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Solution: Use binary packages<\/p>\n<pre class=\"wp-block-code\"><code># requirements.txt\npsycopg2-binary==2.9.6  # Pre-compiled!<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>pip install psycopg2-binary==2.9.6\n\nCollecting psycopg2-binary==2.9.6\n  Downloading psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.0 MB)\n\nInstalling collected packages: psycopg2-binary\nSuccessfully installed psycopg2-binary-2.9.6\n\n# Installation: 2 seconds! \u2705<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Binary wheel contents:<\/p>\n<pre class=\"wp-block-code\"><code>psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_x86_64.whl\n\u2502\n\u2514\u2500\u2500 Contains:\n    - _psycopg.cpython-311-x86_64-linux-gnu.so (compiled C extension)\n    - libpq.so.5 (bundled PostgreSQL library)\n    - All Python files\n    \n    Ready to use immediately!<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Caching for Speed<\/h3>\n<p class=\"wp-block-paragraph\">pip cache directory:<\/p>\n<pre class=\"wp-block-code\"><code>~\/.cache\/pip\/\n\u251c\u2500\u2500 http\/        (cached downloads)\n\u2514\u2500\u2500 wheels\/      (built wheels)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">First install:<\/p>\n<pre class=\"wp-block-code\"><code>pip install requests==2.31.0\n\nCollecting requests==2.31.0\n  Downloading requests-2.31.0-py3-none-any.whl (62 kB)\nInstalling...\nDone. (3.2 seconds)\n\nCache saved:\n~\/.cache\/pip\/http\/xyz\/requests-2.31.0-py3-none-any.whl<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Second install (same version):<\/p>\n<pre class=\"wp-block-code\"><code>pip install requests==2.31.0\n\nCollecting requests==2.31.0\n  Using cached requests-2.31.0-py3-none-any.whl (62 kB)\nInstalling...\nDone. (0.4 seconds)\n\n# 8x faster! \u2705<\/code><\/pre>\n<p class=\"wp-block-paragraph\">In Jenkins, cache persists between builds if workspace preserved!<\/p>\n<h3 class=\"wp-block-heading\">Why Virtual Environments Matter<\/h3>\n<p class=\"wp-block-paragraph\">Without virtual environment:<\/p>\n<pre class=\"wp-block-code\"><code># System Python 3.11.4\n\npip install flask==2.3.2\n# Installs to \/usr\/local\/lib\/python3.11\/site-packages\/\n\npip install flask==3.0.0  # Newer project needs different version\n# OVERWRITES flask 2.3.2!\n\n# Old project breaks! \u274c<\/code><\/pre>\n<p class=\"wp-block-paragraph\">With virtual environment:<\/p>\n<pre class=\"wp-block-code\"><code># Project 1\npython3 -m venv project1-env\nsource project1-env\/bin\/activate\npip install flask==2.3.2\n# Installs to project1-env\/lib\/python3.11\/site-packages\/\n\n# Project 2\npython3 -m venv project2-env\nsource project2-env\/bin\/activate\npip install flask==3.0.0\n# Installs to project2-env\/lib\/python3.11\/site-packages\/\n\n# Both projects work! \u2705<\/code><\/pre>\n<p class=\"wp-block-paragraph\">For Jenkins (Docker handles isolation):<\/p>\n<p class=\"wp-block-paragraph\">In my pipeline, I use Docker so virtual environment is optional:<\/p>\n<pre class=\"wp-block-code\"><code>FROM python:3.11-slim\n\nWORKDIR \/app\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Each Docker container is already isolated!<\/p>\n<h3 class=\"wp-block-heading\">Language 4: PHP\/Composer &#8211; Autoloading Magic<\/h3>\n<h3 class=\"wp-block-heading\">Complete Pipeline Code<\/h3>\n<pre class=\"wp-block-code\"><code>pipeline {\n    agent any\n    \n    options {\n        buildDiscarder logRotator(daysToKeepStr: '7', numToKeepStr: '2')\n    }\n    \n    environment {\n        dockercred = credentials('docker-hub')\n    }\n    \n    stages {\n        stage('CheckOut') {\n            steps {\n                checkout scmGit(\n                    branches: [[name: '*\/main']], \n                    userRemoteConfigs: [[url: 'https:\/\/github.com\/mohammadrezachegini\/PHP-jenkins\/']]\n                )\n            }\n        }\n        \n        stage('build stage') {\n            steps {\n                sh 'php -v'\n                sh 'composer install'\n            }\n        }\n        \n        stage('Docker build and push') {\n            steps {\n                sh 'docker build -t lowyiiii\/jenkins-php:latest .'\n                sh 'echo $dockercred_PSW | docker login -u $dockercred_USR --password-stdin'\n                sh 'docker push lowyiiii\/jenkins-php:latest'\n            }\n        }\n        \n        stage('kubectl deployment') {\n            steps {\n                sh 'kubectl apply -f deploymen.yaml'\n            }\n        }\n    }\n}<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Deep Dive: Composer Dependency Resolution<\/h3>\n<p class=\"wp-block-paragraph\">Example composer.json:<\/p>\n<pre class=\"wp-block-code\"><code>{\n    \"require\": {\n        \"symfony\/http-foundation\": \"^6.0\",\n        \"guzzlehttp\/guzzle\": \"^7.5\",\n        \"monolog\/monolog\": \"~3.0\"\n    },\n    \"require-dev\": {\n        \"phpunit\/phpunit\": \"^10.0\"\n    }\n}<\/code><\/pre>\n<h3 class=\"wp-block-heading\">composer install Process<\/h3>\n<pre class=\"wp-block-code\"><code>composer install<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Step 1: Parse composer.json and composer.lock<\/p>\n<p class=\"wp-block-paragraph\">If composer.lock exists:<\/p>\n<p class=\"wp-block-paragraph\">Use exact versions from lock file \u2705<\/p>\n<p class=\"wp-block-paragraph\">Else:<\/p>\n<p class=\"wp-block-paragraph\">Resolve versions from composer.json<\/p>\n<p class=\"wp-block-paragraph\">Create composer.lock<\/p>\n<p class=\"wp-block-paragraph\">Step 2: SAT Solver Dependency Resolution<\/p>\n<p class=\"wp-block-paragraph\">Composer uses Boolean satisfiability (SAT) solver:<\/p>\n<p class=\"wp-block-paragraph\">Problem: Find package versions satisfying ALL constraints<\/p>\n<p class=\"wp-block-paragraph\">Constraints:<\/p>\n<ol class=\"wp-block-list\"><li>symfony\/http-foundation: ^6.0<\/li><li>symfony\/http-foundation requires symfony\/polyfill-mbstring: ~1.1<\/li><li>symfony\/polyfill-mbstring requires php: &gt;=7.1<\/li><li>guzzlehttp\/guzzle: ^7.5<\/li><li>guzzlehttp\/guzzle requires guzzlehttp\/psr7: ^2.0<\/li><li>guzzlehttp\/psr7 requires psr\/http-message: ^1.0 || ^2.0<\/li><li>&#8230;<\/li><\/ol>\n<p class=\"wp-block-paragraph\">SAT solver tries combinations until ALL constraints satisfied:<\/p>\n<p class=\"wp-block-paragraph\">Solution found:<\/p>\n<ul class=\"wp-block-list\"><li>symfony\/http-foundation: 6.0.8<\/li><li>symfony\/polyfill-mbstring: 1.27.0<\/li><li>guzzlehttp\/guzzle: 7.5.0<\/li><li>guzzlehttp\/psr7: 2.4.5<\/li><li>psr\/http-message: 2.0<\/li><\/ul>\n<p class=\"wp-block-paragraph\">This is NP-complete problem! Can take 30+ seconds for complex apps.<\/p>\n<p class=\"wp-block-paragraph\">Step 3: Download packages<\/p>\n<p class=\"wp-block-paragraph\">For each package, Composer:<\/p>\n<p class=\"wp-block-paragraph\">1. Queries Packagist.org:<\/p>\n<pre class=\"wp-block-code\"><code>GET https:\/\/repo.packagist.org\/p2\/symfony\/http-foundation.json\n\nResponse:\n{\n  \"packages\": {\n    \"symfony\/http-foundation\": [\n      {\n        \"version\": \"v6.0.8\",\n        \"dist\": {\n          \"type\": \"zip\",\n          \"url\": \"https:\/\/api.github.com\/repos\/symfony\/http-foundation\/zipball\/c9c86b...\",\n          \"reference\": \"c9c86b7b9313ee7d8dd9...\"\n        },\n        \"source\": {\n          \"type\": \"git\",\n          \"url\": \"https:\/\/github.com\/symfony\/http-foundation.git\",\n          \"reference\": \"c9c86b7b9313ee7d8dd9...\"\n        }\n      }\n    ]\n  }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">2. Downloads ZIP or clones Git:<\/p>\n<pre class=\"wp-block-code\"><code># Composer prefers dist (ZIP) for speed:\nDownloading symfony\/http-foundation (v6.0.8)\n  - Installing symfony\/http-foundation (v6.0.8): Extracting archive\n\n# ZIP downloaded from GitHub API\n# Extracted to vendor\/symfony\/http-foundation\/<\/code><\/pre>\n<p class=\"wp-block-paragraph\">3. Verifies Git reference:<\/p>\n<pre class=\"wp-block-code\"><code># composer.lock stores Git commit hash:\n{\n  \"packages\": [\n    {\n      \"name\": \"symfony\/http-foundation\",\n      \"version\": \"v6.0.8\",\n      \"source\": {\n        \"reference\": \"c9c86b7b9313ee7d8dd9...\"  \u2190 Git commit SHA\n      }\n    }\n  ]\n}\n\n# After download, Composer verifies:\nextracted_git_hash = git rev-parse HEAD\nexpected_hash = \"c9c86b7b9313ee7d8dd9...\"\n\nif extracted_git_hash == expected_hash:\n    \u2705 Installation valid\nelse:\n    \u274c ERROR: Package corrupted or tampered!<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Step 4: Generate autoloader<\/p>\n<pre class=\"wp-block-code\"><code>composer install\n\n...\nGenerating autoload files\nGenerated autoload files<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What this creates:<\/p>\n<pre class=\"wp-block-code\"><code>vendor\/\n\u251c\u2500\u2500 autoload.php                (main autoloader entry)\n\u251c\u2500\u2500 composer\/\n\u2502   \u251c\u2500\u2500 autoload_classmap.php  (class \u2192 file mapping)\n\u2502   \u251c\u2500\u2500 autoload_namespaces.php (PSR-0 namespaces)\n\u2502   \u251c\u2500\u2500 autoload_psr4.php       (PSR-4 namespaces)\n\u2502   \u251c\u2500\u2500 autoload_files.php      (files to auto-include)\n\u2502   \u251c\u2500\u2500 autoload_static.php     (optimized static array)\n\u2502   \u2514\u2500\u2500 ClassLoader.php          (autoloading logic)\n\u2514\u2500\u2500 symfony\/\n    \u2514\u2500\u2500 http-foundation\/<\/code><\/pre>\n<h3 class=\"wp-block-heading\">PSR-4 Autoloading Deep Dive<\/h3>\n<p class=\"wp-block-paragraph\">composer.json autoload section:<\/p>\n<pre class=\"wp-block-code\"><code>{\n    \"autoload\": {\n        \"psr-4\": {\n            \"App\\\\\": \"src\/\",\n            \"App\\\\Controllers\\\\\": \"src\/Controllers\/\"\n        }\n    }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Generated autoload_psr4.php:<\/p>\n<pre class=\"wp-block-code\"><code>&lt;?php\nreturn array(\n    'App\\\\Controllers\\\\' =&gt; array($baseDir . '\/src\/Controllers'),\n    'App\\\\' =&gt; array($baseDir . '\/src'),\n    'Symfony\\\\Component\\\\HttpFoundation\\\\' =&gt; \n        array($vendorDir . '\/symfony\/http-foundation'),\n);<\/code><\/pre>\n<p class=\"wp-block-paragraph\">How autoloading works at runtime:<\/p>\n<pre class=\"wp-block-code\"><code>&lt;?php\nrequire 'vendor\/autoload.php';\n\n\/\/ You call:\n$controller = new App\\Controllers\\UserController();\n\n\/\/ Behind the scenes:\n\/\/ 1. PHP doesn't find class, triggers spl_autoload()\n\/\/ 2. ClassLoader::loadClass('App\\\\Controllers\\\\UserController')\n\/\/ 3. Searches PSR-4 mapping:\n\/\/      Namespace: App\\Controllers\\\n\/\/      Base directory: src\/Controllers\/\n\/\/      Class: UserController\n\/\/      File: src\/Controllers\/UserController.php\n\/\/ 4. Full path: \/var\/www\/src\/Controllers\/UserController.php\n\/\/ 5. require_once '\/var\/www\/src\/Controllers\/UserController.php';\n\/\/ 6. Class now loaded! \u2705<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Detailed algorithm:<\/p>\n<pre class=\"wp-block-code\"><code>\/\/ Simplified ClassLoader logic\nclass ClassLoader {\n    private $prefixesPsr4 = [\n        'App\\\\Controllers\\\\' =&gt; ['\/var\/www\/src\/Controllers'],\n        'App\\\\' =&gt; ['\/var\/www\/src']\n    ];\n    \n    public function loadClass($class) {\n        \/\/ $class = 'App\\\\Controllers\\\\UserController'\n        \n        \/\/ Try each prefix\n        foreach ($this-&gt;prefixesPsr4 as $prefix =&gt; $dirs) {\n            \/\/ Check if class starts with prefix\n            if (strpos($class, $prefix) === 0) {\n                \/\/ $prefix = 'App\\\\Controllers\\\\'\n                \/\/ $dirs = ['\/var\/www\/src\/Controllers']\n                \n                \/\/ Remove prefix from class name\n                $relativeClass = substr($class, strlen($prefix));\n                \/\/ $relativeClass = 'UserController'\n                \n                foreach ($dirs as $dir) {\n                    \/\/ Convert namespace separators to directory separators\n                    $file = $dir . '\/' . str_replace('\\\\', '\/', $relativeClass) . '.php';\n                    \/\/ $file = '\/var\/www\/src\/Controllers\/UserController.php'\n                    \n                    if (file_exists($file)) {\n                        require $file;\n                        return true;\n                    }\n                }\n            }\n        }\n        return false;\n    }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Optimization: Autoload classmap<\/p>\n<p class=\"wp-block-paragraph\">For production, generate classmap for speed:<\/p>\n<pre class=\"wp-block-code\"><code>composer dump-autoload --optimize<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Creates autoload_classmap.php:<\/p>\n<pre class=\"wp-block-code\"><code>&lt;?php\nreturn array(\n    'App\\\\Controllers\\\\UserController' =&gt; \n        $baseDir . '\/src\/Controllers\/UserController.php',\n    'App\\\\Models\\\\User' =&gt; \n        $baseDir . '\/src\/Models\/User.php',\n    'App\\\\Services\\\\AuthService' =&gt; \n        $baseDir . '\/src\/Services\/AuthService.php',\n    \/\/ ... all classes mapped\n);<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Performance:<\/p>\n<p class=\"wp-block-paragraph\">PSR-4 autoloading (dynamic):<\/p>\n<ul class=\"wp-block-list\"><li>Search through prefixes<\/li><li>Build file path<\/li><li>Check if file exists<\/li><li>Require file<\/li><li>Time: ~1.5ms per class<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Classmap autoloading (optimized):<\/p>\n<ul class=\"wp-block-list\"><li>Direct array lookup<\/li><li>Require file<\/li><li>Time: ~0.2ms per class<\/li><\/ul>\n<p class=\"wp-block-paragraph\">7x faster! \u2705<\/p>\n<h3 class=\"wp-block-heading\">composer install vs composer update<\/h3>\n<p class=\"wp-block-paragraph\">composer install:<\/p>\n<pre class=\"wp-block-code\"><code>composer install<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Algorithm:<\/p>\n<ol class=\"wp-block-list\"><li>Check composer.lock exists<\/li><li>Read versions from lock file<\/li><li>Download EXACT versions<\/li><li>Never modify composer.lock<\/li><li>Fail if composer.json and lock file inconsistent<\/li><\/ol>\n<p class=\"wp-block-paragraph\">Use case: CI\/CD, production deployment<\/p>\n<p class=\"wp-block-paragraph\">composer update:<\/p>\n<pre class=\"wp-block-code\"><code>composer update<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Algorithm:<\/p>\n<ol class=\"wp-block-list\"><li>Read composer.json<\/li><li>Resolve latest compatible versions<\/li><li>Download packages<\/li><li>Update composer.lock with new versions<\/li><li>Always modifies lock file<\/li><\/ol>\n<p class=\"wp-block-paragraph\">Use case: Development, updating dependencies<\/p>\n<p class=\"wp-block-paragraph\">Real example:<\/p>\n<p class=\"wp-block-paragraph\">composer.json:<\/p>\n<pre class=\"wp-block-code\"><code>\"symfony\/http-foundation\": \"^6.0\"<\/code><\/pre>\n<p class=\"wp-block-paragraph\">composer.lock (3 months old):<\/p>\n<pre class=\"wp-block-code\"><code>\"symfony\/http-foundation\": \"v6.0.8\"<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Today, v6.3.5 is available.<\/p>\n<p class=\"wp-block-paragraph\">composer install:<\/p>\n<ul class=\"wp-block-list\"><li>Installs v6.0.8 (from lock file)<\/li><li>Production consistency \u2705<\/li><\/ul>\n<p class=\"wp-block-paragraph\">composer update:<\/p>\n<ul class=\"wp-block-list\"><li>Resolves &#8220;^6.0&#8221; constraint<\/li><li>Finds v6.3.5 (latest 6.x)<\/li><li>Updates lock file to v6.3.5<\/li><li>Development gets new features \u2705<\/li><\/ul>\n<p class=\"wp-block-paragraph\">In Jenkins, ALWAYS use composer install!<\/p>\n<pre class=\"wp-block-code\"><code>stage('build stage') {\n    steps {\n        sh 'composer install --no-dev --optimize-autoloader'\n        \/\/ --no-dev: Skip dev dependencies\n        \/\/ --optimize-autoloader: Generate classmap for speed\n    }\n}<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Multi-Environment Deployment Strategy<\/h3>\n<h3 class=\"wp-block-heading\">Technical Implementation<\/h3>\n<p class=\"wp-block-paragraph\">All 4 languages use same deployment pattern:<\/p>\n<pre class=\"wp-block-code\"><code>stage('Deploy to Dev') {\n    steps {\n        sh 'kubectl apply -f deployment.yaml -n dev'\n    }\n}\n\nstage('Deploy to Staging') {\n    steps {\n        sh 'kubectl apply -f deployment.yaml -n staging'\n    }\n}\n\nstage('Deploy to Production') {\n    steps {\n        script {\n            def approval = input(\n                id: 'Deployment',\n                message: 'Deploy to production?',\n                submitter: 'admin'\n            )\n        }\n        sh 'kubectl apply -f deployment.yaml -n prod'\n    }\n}<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Deep Dive: Manual Approval Gate<\/h3>\n<p class=\"wp-block-paragraph\">What happens when pipeline reaches input():<\/p>\n<p class=\"wp-block-paragraph\">1. Pipeline execution pauses:<\/p>\n<p class=\"wp-block-paragraph\">Jenkins stores:<\/p>\n<ul class=\"wp-block-list\"><li>Pipeline state to disk<\/li><li>All variables in memory<\/li><li>Current stage information<\/li><li>Workspace location<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Process:<\/p>\n<pre class=\"wp-block-code\"><code>Main Jenkins process \u2192 Fork pipeline executor \u2192 Wait for user input<\/code><\/pre>\n<p class=\"wp-block-paragraph\">2. UI shows approval dialog:<\/p>\n<pre class=\"wp-block-code\"><code>+---------------------------------------------+\n|  Deploy to production?                      |\n|                                             |\n|  [Proceed]  [Abort]                        |\n|                                             |\n|  Only visible to: admin role               |\n+---------------------------------------------+<\/code><\/pre>\n<p class=\"wp-block-paragraph\">3. RBAC check:<\/p>\n<pre class=\"wp-block-code\"><code>submitter: 'admin'<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Jenkins checks:<\/p>\n<p class=\"wp-block-paragraph\">Current user: john@example.com<\/p>\n<p class=\"wp-block-paragraph\">User roles: [developer, deployer, admin]<\/p>\n<p class=\"wp-block-paragraph\">Required role: admin<\/p>\n<p class=\"wp-block-paragraph\">Permission check: &#8216;admin&#8217; in user.roles \u2192 TRUE \u2705<\/p>\n<p class=\"wp-block-paragraph\">Show buttons: [Proceed] [Abort]<\/p>\n<p class=\"wp-block-paragraph\">If user doesn&#8217;t have admin role:<\/p>\n<p class=\"wp-block-paragraph\">Permission check: &#8216;admin&#8217; in user.roles \u2192 FALSE \u274c<\/p>\n<p class=\"wp-block-paragraph\">UI shows:<\/p>\n<p class=\"wp-block-paragraph\">&#8220;Waiting for approval from admin&#8221;<\/p>\n<p class=\"wp-block-paragraph\">No buttons visible<\/p>\n<p class=\"wp-block-paragraph\">4. On Proceed:<\/p>\n<p class=\"wp-block-paragraph\">Pipeline resumes:<\/p>\n<ul class=\"wp-block-list\"><li>Loads saved state from disk<\/li><li>Restores variables<\/li><li>Continues to next step<\/li><li>Executes deployment<\/li><\/ul>\n<p class=\"wp-block-paragraph\">5. On Abort:<\/p>\n<p class=\"wp-block-paragraph\">Pipeline aborted:<\/p>\n<ul class=\"wp-block-list\"><li>Build marked as ABORTED<\/li><li>Cleanup steps run (if post{} defined)<\/li><li>No deployment happens<\/li><li>Workspace preserved for investigation<\/li><\/ul>\n<p class=\"wp-block-paragraph\">6. Timeout protection:<\/p>\n<pre class=\"wp-block-code\"><code>stage('Deploy to Production') {\n    steps {\n        timeout(time: 2, unit: 'HOURS') {\n            script {\n                def approval = input(...)\n            }\n        }\n        sh 'kubectl apply -f deployment.yaml -n prod'\n    }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">If no one approves within 2 hours:<\/p>\n<p class=\"wp-block-paragraph\">Pipeline status: ABORTED<\/p>\n<p class=\"wp-block-paragraph\">Reason: &#8220;Timeout waiting for approval&#8221;<\/p>\n<p class=\"wp-block-paragraph\">Executor released \u2705<\/p>\n<h3 class=\"wp-block-heading\">Deep Dive: Kubernetes Namespace Isolation<\/h3>\n<p class=\"wp-block-paragraph\">deployment.yaml for each environment:<\/p>\n<pre class=\"wp-block-code\"><code># deployment-dev.yaml\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: java-app\n  namespace: dev\nspec:\n  replicas: 1\n  template:\n    spec:\n      containers:\n      - name: java-app\n        image: lowyiiii\/jenkins-java:latest\n        resources:\n          requests:\n            memory: \"256Mi\"\n            cpu: \"250m\"<\/code><\/pre>\n<pre class=\"wp-block-code\"><code># deployment-prod.yaml\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: java-app\n  namespace: prod\nspec:\n  replicas: 3  # High availability\n  template:\n    spec:\n      containers:\n      - name: java-app\n        image: lowyiiii\/jenkins-java:47  # Pinned version!\n        resources:\n          requests:\n            memory: \"1Gi\"\n            cpu: \"1000m\"\n          limits:\n            memory: \"2Gi\"\n            cpu: \"2000m\"<\/code><\/pre>\n<p class=\"wp-block-paragraph\">How namespaces provide isolation:<\/p>\n<p class=\"wp-block-paragraph\">1. Resource isolation:<\/p>\n<pre class=\"wp-block-code\"><code># Resource quota for dev namespace\napiVersion: v1\nkind: ResourceQuota\nmetadata:\n  name: dev-quota\n  namespace: dev\nspec:\n  hard:\n    requests.cpu: \"10\"\n    requests.memory: \"20Gi\"\n    requests.storage: \"100Gi\"\n    persistentvolumeclaims: \"10\"\n    pods: \"50\"<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What this does:<\/p>\n<p class=\"wp-block-paragraph\">Total cluster resources: 64 CPU, 256GB RAM<\/p>\n<p class=\"wp-block-paragraph\">Dev namespace limited to: 10 CPU, 20GB RAM<\/p>\n<p class=\"wp-block-paragraph\">If dev team tries to deploy too many pods:<\/p>\n<p class=\"wp-block-paragraph\">Error: exceeded quota: requested 11 CPU, limit is 10 CPU<\/p>\n<p class=\"wp-block-paragraph\">Production remains unaffected! \u2705<\/p>\n<p class=\"wp-block-paragraph\">2. Network isolation:<\/p>\n<pre class=\"wp-block-code\"><code># Network policy - deny all traffic between namespaces\napiVersion: networking.k8s.io\/v1\nkind: NetworkPolicy\nmetadata:\n  name: deny-cross-namespace\n  namespace: prod\nspec:\n  podSelector: {}\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector: {}  # Allow from same namespace only<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Result:<\/p>\n<pre class=\"wp-block-code\"><code># Pod in dev namespace\ncurl http:\/\/java-app.prod.svc.cluster.local:8080\n# Connection timeout! \u2705 Cannot reach prod\n\n# Pod in prod namespace\ncurl http:\/\/java-app.prod.svc.cluster.local:8080\n# Success! \u2705 Can reach same namespace<\/code><\/pre>\n<p class=\"wp-block-paragraph\">3. RBAC isolation:<\/p>\n<pre class=\"wp-block-code\"><code># Role for dev namespace\napiVersion: rbac.authorization.k8s.io\/v1\nkind: Role\nmetadata:\n  name: developer\n  namespace: dev\nrules:\n- apiGroups: [\"\", \"apps\"]\n  resources: [\"pods\", \"deployments\", \"services\"]\n  verbs: [\"get\", \"list\", \"create\", \"update\", \"patch\", \"delete\"]\n---\n# RoleBinding\napiVersion: rbac.authorization.k8s.io\/v1\nkind: RoleBinding\nmetadata:\n  name: developer-binding\n  namespace: dev\nsubjects:\n- kind: User\n  name: john@example.com\nroleRef:\n  kind: Role\n  name: developer<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What this does:<\/p>\n<pre class=\"wp-block-code\"><code># As john@example.com\n\nkubectl get pods -n dev\n# Success! \u2705\n\nkubectl delete pod java-app-xyz -n dev\n# Success! \u2705\n\nkubectl get pods -n prod\n# Error: User \"john@example.com\" cannot list pods in namespace \"prod\" \u2705<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Performance Benchmarks: Real Numbers<\/h3>\n<p class=\"wp-block-paragraph\">From my home lab (Intel i5-11400, 16GB RAM, NVMe SSD, 100Mbps internet):<\/p>\n<h3 class=\"wp-block-heading\">Build Time Comparison<\/h3>\n<p class=\"wp-block-paragraph\">Checkout \u2192 Java 3s | Node.js 3s | Python 3s | PHP 3s<\/p>\n<p class=\"wp-block-paragraph\">Dependencies (cold) \u2192 Java 48s | Node.js 15s | Python 12s | PHP 18s<\/p>\n<p class=\"wp-block-paragraph\">Dependencies (warm) \u2192 Java 8s | Node.js 3s | Python 2s | PHP 4s<\/p>\n<p class=\"wp-block-paragraph\">Tests \u2192 Java 32s | Node.js 8s | Python 6s | PHP 12s<\/p>\n<p class=\"wp-block-paragraph\">Build \/ Package \u2192 Java 12s | Node.js 0s | Python 0s | PHP 0s<\/p>\n<p class=\"wp-block-paragraph\">Docker build \u2192 Java 2m25s | Node.js 1m35s | Python 1m48s | PHP 2m05s<\/p>\n<p class=\"wp-block-paragraph\">Docker push \u2192 Java 3m12s | Node.js 1m45s | Python 1m52s | PHP 2m15s<\/p>\n<p class=\"wp-block-paragraph\">Total (cold) \u2192 Java 9m20s | Node.js 4m49s | Python 5m03s | PHP 6m56s<\/p>\n<p class=\"wp-block-paragraph\">Total (warm) \u2192 Java 8m40s | Node.js 4m37s | Python 4m53s | PHP 6m42s<\/p>\n<h3 class=\"wp-block-heading\">Why These Differences?<\/h3>\n<p class=\"wp-block-paragraph\">Java (Slowest overall):<\/p>\n<ul class=\"wp-block-list\"><li>Maven downloads many transitive dependencies<\/li><li>Compilation takes time (Java \u2192 bytecode)<\/li><li>Unit tests run in forked JVM<\/li><li>Large Docker base image (openjdk:11 = 410MB)<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Node.js (Fastest):<\/p>\n<ul class=\"wp-block-list\"><li>npm ci is highly optimized<\/li><li>No compilation step (interpreted)<\/li><li>Small Docker base (node:18-alpine = 145MB)<\/li><li>Fast package registry (npmjs.com)<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Python (Fast):<\/p>\n<ul class=\"wp-block-list\"><li>pip is efficient for pure Python packages<\/li><li>C extensions slow down some installs<\/li><li>Medium Docker base (python:3.11-slim = 180MB)<\/li><\/ul>\n<p class=\"wp-block-paragraph\">PHP (Moderate):<\/p>\n<ul class=\"wp-block-list\"><li>Composer SAT solver takes time<\/li><li>No compilation (interpreted)<\/li><li>Large Docker base (php:8.1-apache = 465MB)<\/li><\/ul>\n<h3 class=\"wp-block-heading\">Memory Usage During Build<\/h3>\n<p class=\"wp-block-paragraph\">Build tools \u2022 Java: 650 MB \u2022 Node.js: 120 MB \u2022 Python: 85 MB \u2022 PHP: 180 MB<\/p>\n<p class=\"wp-block-paragraph\">Test execution \u2022 Java: 420 MB \u2022 Node.js: 95 MB \u2022 Python: 65 MB \u2022 PHP: 110 MB<\/p>\n<p class=\"wp-block-paragraph\">Docker build \u2022 Java: 2.1 GB \u2022 Node.js: 1.2 GB \u2022 Python: 1.4 GB \u2022 PHP: 1.8 GB<\/p>\n<p class=\"wp-block-paragraph\">Peak total memory usage \u2022 Java: 3.2 GB \u2022 Node.js: 1.4 GB \u2022 Python: 1.5 GB \u2022 PHP: 2.1GB<\/p>\n<p class=\"wp-block-paragraph\">Why Java uses more memory:<\/p>\n<ul class=\"wp-block-list\"><li>Maven heap: -Xmx512m default<\/li><li>Surefire test fork: -Xmx256m<\/li><li>Docker layer caching<\/li><li>Compilation metadata<\/li><\/ul>\n<h3 class=\"wp-block-heading\">Disk Space: Dependency Size<\/h3>\n<p class=\"wp-block-paragraph\">Java (Maven dependencies):<\/p>\n<p class=\"wp-block-paragraph\">~\/.m2\/repository\/: 1.2GB (after building 10 projects)<\/p>\n<p class=\"wp-block-paragraph\">Single project dependencies: ~120MB<\/p>\n<p class=\"wp-block-paragraph\">Node.js (npm):<\/p>\n<p class=\"wp-block-paragraph\">node_modules\/: 350MB (typical React app)<\/p>\n<p class=\"wp-block-paragraph\">~\/.npm cache: 2.5GB (after 10 projects)<\/p>\n<p class=\"wp-block-paragraph\">Python (pip):<\/p>\n<p class=\"wp-block-paragraph\">site-packages\/: 180MB (Flask app with common libs)<\/p>\n<p class=\"wp-block-paragraph\">~\/.cache\/pip: 1.1GB (after 10 projects)<\/p>\n<p class=\"wp-block-paragraph\">PHP (Composer):<\/p>\n<p class=\"wp-block-paragraph\">vendor\/: 280MB (Symfony full-stack)<\/p>\n<p class=\"wp-block-paragraph\">~\/.composer\/cache: 890MB (after 10 projects)<\/p>\n<h3 class=\"wp-block-heading\">Security Best Practices Learned<\/h3>\n<h3 class=\"wp-block-heading\">1. Never Hardcode Credentials<\/h3>\n<p class=\"wp-block-paragraph\">WRONG \u274c:<\/p>\n<pre class=\"wp-block-code\"><code>stage('Docker push') {\n    steps {\n        sh 'docker login -u myusername -p SuperSecret123'\n        sh 'docker push myimage:latest'\n    }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Problems:<\/p>\n<ol class=\"wp-block-list\"><li>Password visible in:<\/li><li>Anyone with read access sees password<\/li><li>Password rotation requires Jenkinsfile update<\/li><\/ol>\n<ul class=\"wp-block-list\"><li>Console log \u2713<\/li><li>Build history \u2713<\/li><li>Process list (ps aux) \u2713<\/li><li>Jenkins configuration XML \u2713<\/li><li>Git history if Jenkinsfile committed \u2713<\/li><\/ul>\n<p class=\"wp-block-paragraph\">RIGHT \u2705:<\/p>\n<pre class=\"wp-block-code\"><code>environment {\n    dockercred = credentials('docker-hub')\n}\n\nstage('Docker push') {\n    steps {\n        sh 'echo $dockercred_PSW | docker login -u $dockercred_USR --password-stdin'\n        sh 'docker push myimage:latest'\n    }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Why this is secure:<\/p>\n<ol class=\"wp-block-list\"><li>Credentials stored in Jenkins credential store (encrypted)<\/li><li>$dockercred_PSW masked in console output as ****<\/li><li>&#8211;password-stdin prevents password in process list<\/li><li>Credentials() function creates temporary variables:<\/li><li>RBAC controls who can read credentials<\/li><li>Password rotation in Jenkins UI only<\/li><\/ol>\n<ul class=\"wp-block-list\"><li>$dockercred_USR = username<\/li><li>$dockercred_PSW = password<\/li><li>Variables destroyed after stage completes<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Technical: How &#8211;password-stdin works:<\/p>\n<pre class=\"wp-block-code\"><code># Without --password-stdin:\ndocker login -u user -p password123\n# Process list shows: docker login -u user -p password123\n# Anyone running 'ps aux' sees password! \u274c\n\n# With --password-stdin:\necho password123 | docker login -u user --password-stdin\n# Process list shows: docker login -u user --password-stdin\n# Password comes via pipe (invisible to ps) \u2705<\/code><\/pre>\n<h3 class=\"wp-block-heading\">2. AWS Credential Verification<\/h3>\n<pre class=\"wp-block-code\"><code>stage('AWS verification') {\n    steps {\n        sh 'aws sts get-caller-identity'\n    }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What this command does:<\/p>\n<pre class=\"wp-block-code\"><code>aws sts get-caller-identity<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Response:<\/p>\n<pre class=\"wp-block-code\"><code>{\n    \"UserId\": \"AIDAI23LPQQ7W4EXAMPLE\",\n    \"Account\": \"381492204839\",\n    \"Arn\": \"arn:aws:iam::381492204839:user\/jenkins-deploy\"\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Why verify credentials:<\/p>\n<p class=\"wp-block-paragraph\">Without verification:<\/p>\n<ol class=\"wp-block-list\"><li>Pipeline runs for 5 minutes<\/li><li>Builds Docker image<\/li><li>Pushes to Docker Hub<\/li><li>Tries kubectl deployment to AWS EKS<\/li><li>ERROR: AWS credentials expired! \u274c<\/li><li>Wasted 5 minutes, failed build<\/li><\/ol>\n<p class=\"wp-block-paragraph\">With verification (at start):<\/p>\n<ol class=\"wp-block-list\"><li>Check credentials (2 seconds)<\/li><li>ERROR: AWS credentials expired! \u274c<\/li><li>Pipeline fails immediately<\/li><li>Fix credentials<\/li><li>Retry<\/li><li>Saved 4 minutes 58 seconds! \u2705<\/li><\/ol>\n<p class=\"wp-block-paragraph\">Also shows:<\/p>\n<ul class=\"wp-block-list\"><li>Which IAM user\/role is being used<\/li><li>Which AWS account<\/li><li>Helps debug permission issues<\/li><\/ul>\n<h3 class=\"wp-block-heading\">3. Least Privilege Principle<\/h3>\n<p class=\"wp-block-paragraph\">My Jenkins IAM policy for EKS deployment:<\/p>\n<pre class=\"wp-block-code\"><code>{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"eks:DescribeCluster\",\n        \"eks:ListClusters\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"ecr:GetAuthorizationToken\",\n        \"ecr:BatchCheckLayerAvailability\",\n        \"ecr:GetDownloadUrlForLayer\",\n        \"ecr:BatchGetImage\",\n        \"ecr:PutImage\",\n        \"ecr:InitiateLayerUpload\",\n        \"ecr:UploadLayerPart\",\n        \"ecr:CompleteLayerUpload\"\n      ],\n      \"Resource\": \"arn:aws:ecr:us-east-1:381492204839:repository\/jenkins-*\"\n    }\n  ]\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\">What this policy does:<\/p>\n<p class=\"wp-block-paragraph\">Allows:<\/p>\n<ul class=\"wp-block-list\"><li>Read EKS cluster info (needed for kubectl)<\/li><li>Push Docker images to ECR (only repos starting with &#8220;jenkins-&#8220;)<\/li><li>Pull Docker images from ECR (for deployments)<\/li><\/ul>\n<p class=\"wp-block-paragraph\">Denies:<\/p>\n<ul class=\"wp-block-list\"><li>Create\/Delete EKS clusters<\/li><li>Modify IAM roles<\/li><li>Access S3 buckets<\/li><li>Launch EC2 instances<\/li><li>Push to other ECR repos<\/li><\/ul>\n<p class=\"wp-block-paragraph\">If compromised:<\/p>\n<p class=\"wp-block-paragraph\">Attacker can only push Docker images to jenkins-* repos<\/p>\n<p class=\"wp-block-paragraph\">Cannot delete production data \u2705<\/p>\n<p class=\"wp-block-paragraph\">Cannot create expensive resources \u2705<\/p>\n<p class=\"wp-block-paragraph\">Cannot escalate privileges \u2705<\/p>\n<h3 class=\"wp-block-heading\">4. Image Tagging Strategy<\/h3>\n<p class=\"wp-block-paragraph\">Build number tagging:<\/p>\n<pre class=\"wp-block-code\"><code>sh 'docker build -t myimage:${BUILD_NUMBER} .'\nsh 'docker build -t myimage:latest .'<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Deployment strategy:<\/p>\n<pre class=\"wp-block-code\"><code># development.yaml\nspec:\n  containers:\n  - name: app\n    image: myimage:latest\n    imagePullPolicy: Always  # Always pull to get latest<\/code><\/pre>\n<pre class=\"wp-block-code\"><code># staging.yaml\nspec:\n  containers:\n  - name: app\n    image: myimage:${BUILD_NUMBER}  # Specific version for testing\n    imagePullPolicy: IfNotPresent<\/code><\/pre>\n<pre class=\"wp-block-code\"><code># production.yaml\nspec:\n  containers:\n  - name: app\n    image: myimage:47  # Hardcoded, approved version\n    imagePullPolicy: IfNotPresent<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Benefits:<\/p>\n<ol class=\"wp-block-list\"><li>Audit trail: &#8220;What&#8217;s running in production?&#8221; \u2192 image: myimage:47 \u2192 Check Jenkins build #47 \u2192 Git commit: abc123&#8230; \u2192 Full traceability \u2705<\/li><li>Rollback capability: &#8220;Build #50 has bug!&#8221; kubectl set image deployment\/app app=myimage:49 \u2192 Instant rollback to previous version \u2705<\/li><li>Environment promotion: Dev (latest) \u2192 Test passed \u2192 Staging (build 47) \u2192 Test passed \u2192 Prod (build 47) Same binary tested in all environments! \u2705<\/li><li>Security: &#8220;Which images are running?&#8221; kubectl get pods -o yaml | grep image: \u2192 Easy to verify only approved images running \u2705<\/li><\/ol>\n<h3 class=\"wp-block-heading\">Key Takeaways: Universal Patterns<\/h3>\n<p class=\"wp-block-paragraph\">After building 4 production pipelines, here&#8217;s what I learned:<\/p>\n<h3 class=\"wp-block-heading\">1. The Pattern is Universal<\/h3>\n<pre class=\"wp-block-code\"><code>Checkout \u2192 Build \u2192 Test \u2192 Package \u2192 Deploy<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Works for:<\/p>\n<ul class=\"wp-block-list\"><li>Java with Maven<\/li><li>JavaScript with npm<\/li><li>Python with pip<\/li><li>PHP with Composer<\/li><li>ANY language!<\/li><\/ul>\n<h3 class=\"wp-block-heading\">2. Lock Files Guarantee Consistency<\/h3>\n<pre class=\"wp-block-code\"><code>Maven: pom.xml (loose) + Effective POM (resolved)\nnpm: package.json (loose) + package-lock.json (exact)\npip: requirements.txt (can be exact)\nComposer: composer.json (loose) + composer.lock (exact)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Always commit lock files to git!<\/p>\n<h3 class=\"wp-block-heading\">3. Use CI-Optimized Commands<\/h3>\n<pre class=\"wp-block-code\"><code># Development\nnpm install\ncomposer update\n\n# CI\/CD (Jenkins)\nnpm ci  # 40% faster, deterministic\ncomposer install  # Never modifies lock file\npip install --no-cache-dir  # Saves disk space\nmvn -B package  # Batch mode, no interactive prompts<\/code><\/pre>\n<h3 class=\"wp-block-heading\">4. Docker Provides Language Isolation<\/h3>\n<p class=\"wp-block-paragraph\">No virtual environments needed!<\/p>\n<p class=\"wp-block-paragraph\">Each container = isolated filesystem<\/p>\n<p class=\"wp-block-paragraph\">Python 3.9 in one container<\/p>\n<p class=\"wp-block-paragraph\">Python 3.11 in another container<\/p>\n<p class=\"wp-block-paragraph\">No conflicts! \u2705<\/p>\n<h3 class=\"wp-block-heading\">5. Multi-Environment is Language-Agnostic<\/h3>\n<pre class=\"wp-block-code\"><code>Dev \u2192 Staging \u2192 [Manual Approval] \u2192 Production<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Works same way for:<\/p>\n<ul class=\"wp-block-list\"><li>Java Spring Boot<\/li><li>Node.js Express<\/li><li>Python Flask<\/li><li>PHP Symfony<\/li><\/ul>","protected":false},"excerpt":{"rendered":"<p>Introduction: Universal Pattern, Language-Specific Implementation After building CI\/CD pipelines for four different programming languages, I discovered something critical: the DevOps pattern is&hellip;<\/p>\n","protected":false},"author":2,"featured_media":87,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_rezaops_part":"2","_rezaops_linkedin_url":"https:\/\/www.linkedin.com\/pulse\/multi-language-jenkins-pipelines-technical-guide-java-reza-chegini-xsrjc\/","footnotes":""},"categories":[27,10],"tags":[30,8,45,41,46,47,25],"series":[39],"class_list":["post-85","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ci-cd","category-devops","tag-ci-cd","tag-devops","tag-java","tag-jenkins","tag-node-js","tag-php","tag-python","series-jenkins"],"jetpack_featured_media_url":"https:\/\/rezachegini.com\/wp-content\/uploads\/2026\/10\/multi-language-jenkins-pipelines-cover.png","_links":{"self":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts\/85","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=85"}],"version-history":[{"count":1,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts\/85\/revisions"}],"predecessor-version":[{"id":86,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts\/85\/revisions\/86"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/media\/87"}],"wp:attachment":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=85"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=85"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=85"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fseries&post=85"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}