Chapter 1.4 - Dockerized Jenkins - Linux Deployment and Pipeline Execution
Deploying Jenkins
Due to common network restrictions in some regions, directly pulling Docker images might be challenging. A recommended workaround is to download the Jenkins and Jenkins agent images on a local machine with VPN access and then transfer them to your server.
1. Download and Transfer Jenkins Images
First, pull the necessary Docker images and save them as .tar
files. We’ll use the lts-jdk21
tag to get the latest Long-Term Support version for JDK 21.
# Get the latest LTS Jenkins image for JDK 21
docker pull jenkins/jenkins:lts-jdk21
# Save the Docker image to a local file
docker save -o jenkins-lts-jdk21.tar jenkins/jenkins:lts-jdk21
# Get the latest Jenkins agent image for JDK 21
docker pull jenkins/ssh-agent:latest-jdk21
# Save the Docker image to a local file
docker save -o jenkins-ssh-agent-latest-jdk21.tar jenkins/ssh-agent:latest-jdk21
Next, transfer these .tar
files to your Linux server. Replace 152.22.3.186
with your server’s IP address and adjust the scp
port (-P 2222
) if needed.
# Upload to the server
+scp -P 2222 jenkins-lts-jdk21.tar root@152.22.3.186:/home/docker-images
+scp -P 2222 jenkins-ssh-agent-latest-jdk21.tar root@152.22.3.186:/home/docker-images
2. Create a Custom Jenkins Image with Docker Support
To allow Jenkins to interact with the Docker daemon on your host, you’ll need a custom Jenkins image that includes the Docker CLI. Create a Dockerfile
with the following content:
# Use the official Jenkins JDK 21 image as the base
FROM jenkins/jenkins:lts-jdk21
# Switch to root user to install Docker CLI and create necessary groups
USER root
# Install necessary packages for Docker CLI (curl, gnupg2, lsb-release, software-properties-common are often pre-installed or not strictly needed for static binary)
RUN apt-get update && apt-get install -y \
curl \
gnupg2 \
lsb-release \
software-properties-common \
# Clean up apt caches to reduce image size
&& rm -rf /var/lib/apt/lists/*
# Install a specific version of Docker CLI from the static binaries
# Choose a stable version that matches your host's Docker version or a recent one
ARG DOCKER_VERSION=26.1.4
RUN curl -fsSL "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz" | tar xzvf - --strip-components=1 -C /usr/local/bin docker/docker
# Create the 'docker' group if it doesn't exist and add the 'jenkins' user to it
# The GID of the 'docker' group inside the container should ideally match the GID of the 'docker' group on the host.
# You might need to find the GID on your host (e.g., `getent group docker | cut -d: -f3`) and specify it here:
# RUN groupadd -g <HOST_DOCKER_GID> docker || true && usermod -aG docker jenkins
# For simplicity, we assume the default 'docker' group creation is sufficient or pre-exists.
RUN groupadd docker || true && usermod -aG docker jenkins
# Switch back to the jenkins user
USER jenkins
Build your new Jenkins image. Replace my-jenkins-docker-2468-jdk21
with a descriptive tag.
docker build -t my-jenkins-docker:lts-jdk21 .
Save the newly built image and transfer it to your server:
docker save -o my-jenkins-docker-2468-jdk21.tar my-jenkins-docker-2468-jdk21
scp -P 2222 my-jenkins-docker-2468-jdk21.tar root@152.22.3.186:/home/docker-images
3. Load Images on the Server
Once the .tar
files are on your server, load them into Docker:
# On your server
docker load -i /home/docker-images/jenkins-lts-jdk21.tar
docker load -i /home/docker-images/jenkins-ssh-agent-jdk21.tar
docker load -i /home/docker-images/my-jenkins-docker-lts-jdk21.tar
4. Configure and Start Jenkins with Docker Compose
Create a docker-compose.yml
file in a directory on your server. This file defines the Jenkins service and its dependencies.
version: "3.8" # Use a recent Docker Compose file format version
services:
jenkins:
image: my-jenkins-docker-lts-jdk21 # Use your custom Jenkins image
ports:
- "8086:8080" # Map host port 8086 to container port 8080 (Jenkins UI)
- "50000:50000" # Map host port 50000 to container port 50000 (for Jenkins agents)
volumes:
# Persistent storage for Jenkins data
- jenkins_home:/var/jenkins_home
# Mount the Docker socket to allow Jenkins to run Docker commands
- /var/run/docker.sock:/var/run/docker.sock
# Optional: Mount the Docker CLI binary if it's not installed in the image
# - /usr/bin/docker:/usr/bin/docker
# Recommended: Set a restart policy to ensure Jenkins restarts with the Docker daemon
restart: unless-stopped
# Optional: Set resource limits
# deploy:
# resources:
# limits:
# cpus: '2'
# memory: '4G'
ssh-agent:
image: jenkins/ssh-agent:jdk21
# This agent typically connects to Jenkins via JNLP, no direct port mapping needed unless specific use case.
# It's usually managed by Jenkins itself when running pipeline steps.
volumes:
jenkins_home: # Define the named volume for Jenkins data persistence
Navigate to the directory containing docker-compose.yml
and start Jenkins:
docker compose up -d # Start Jenkins in detached mode
To stop and remove Jenkins:
docker compose down # Stop and remove all containers, networks, volumes, and images defined in the Docker Compose file
After starting, Jenkins will be accessible at http://152.22.3.186:8086/
.
Jenkins Configuration
1. Install Plugins
Install the Git Parameter Plug-In to enable automatic loading of repository branches in your CI builds. You can do this via Manage Jenkins > Manage Plugins > Available Plugins.
After installation, configure the Git Parameter in Manage Jenkins > System.
2. Add Credentials
You’ll need to add SSH and SCP credentials for Jenkins to interact with your Git repositories and remote servers.
Generate an SSH key pair on your local machine or Jenkins server (if you plan to use a key generated there):
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_jenkins # Use a specific filename to avoid overwriting
Copy the private key content (from id_rsa_jenkins
or similar file) and add it as a “SSH Username with private key” credential in Jenkins (Manage Jenkins > Manage Credentials > (global) > Add Credentials).
3. Create a New Pipeline Job
Create a new Jenkins job, selecting “Pipeline” as the type.
4. Configure Pipeline Job
In the job configuration, set up the Git Parameter to read branches during the build process.
Under the “Build Triggers” or “General” section (depending on your Jenkins version and installed plugins), you can add a “Choice Parameter” or similar to select between “production” or “development” modes before the build starts. This value can then be accessed in your Pipeline script.
5. Specify Pipeline Script Location
Choose Pipeline script from SCM and configure it to point to your Git repository and the Jenkinsfile
within it. For example, if your Jenkinsfile
is on the config
branch, specify that.
Running Pipeline Scripts
Node.js Environment
To run Node.js applications within your Jenkins pipelines, install the Docker Pipeline plugin. This plugin allows your pipeline to build and use Docker containers.
Before running, ensure you have the required Node.js Docker images available on your Jenkins server. Pull and save them as you did for Jenkins images:
# Pull latest Node.js LTS versions (replace with desired versions)
docker pull node:18
docker pull node:20
docker pull node:22
# Save to local files (example for Node.js 20)
docker save -o node20.tar node:20
# Upload to server
scp -P 2222 node20.tar root@152.22.3.186:/home/docker-images
# On server, load the images
docker load -i /home/docker-images/node20.tar
Here’s an example of a Pipeline script that uses a Node.js 20 Docker image:
pipeline {
agent {
docker {
image 'node:20'
// Mount NPM cache directory for faster builds.
// Using a path within the Jenkins workspace for easy permissions management.
args '-v ${WORKSPACE}/.npm:/home/node/.npm'
}
}
environment {
// Define environment variables for your project
GIT_URL="http://152.22.3.186:8081/mall/h5.git" // Use HTTPS for better security if available
GIT_AUTH = "" // Replace with your Jenkins Credentials ID for Git
GIT_BRANCH = "${branch}" // Parameter from Jenkins job
PROJECT_ENV = "${project_env}" // Parameter from Jenkins job (e.g., "vip", "dev")
VIP_HOST = '152.22.3.186'
VIP_REMOTE_DIR = "/mnt/mall/h5"
LOCAL_BUILD_DIR = "${WORKSPACE}/h5_vip/dist/" // Assuming 'dist' is output dir
}
stages {
stage('Git Checkout') {
steps {
echo "🏆 WORKSPACE: ${WORKSPACE}"
echo "🎯 Branch: ${GIT_BRANCH}"
echo "🏅 Project Environment: ${PROJECT_ENV}"
script {
checkout([
$class: 'GitSCM',
branches: [[name: "${GIT_BRANCH}" ]],
doGenerateSubmoduleConfigurations: false,
extensions: [[$class: 'CleanBeforeCheckout']], // Add clean checkout for consistency
submoduleCfg: [],
userRemoteConfigs: [[
credentialsId: "${GIT_AUTH}",
url: "${GIT_URL}"
]]
])
}
sh 'pwd'
sh 'ls -la'
}
}
stage('Build and Deploy Frontend') {
when {
expression {
// Only proceed if the previous stage was successful or skipped
currentBuild.result == null || currentBuild.result == 'SUCCESS'
}
}
steps {
sh 'pwd'
script {
switch (PROJECT_ENV) {
case "vip":
// Navigate to the project directory, install dependencies, build
sh '''
ls -la
cd h5_vip
npm install --cache /home/node/.npm --registry=https://registry.npmmirror.com/ # Use a reliable registry
npm run build
'''
// Ensure the 'dist' directory exists and rename it as needed
sh 'cd h5_vip && mv dist test_dir'
withCredentials([sshUserPrivateKey(credentialsId: '', // Fill this with your credentials ID
keyFileVariable: 'SSH_KEY')]) {
// Connect to remote server, remove old deployment, and upload new files
sh '''
ssh -i ${SSH_KEY} -o StrictHostKeyChecking=no root@${VIP_HOST} "rm -rf ${VIP_REMOTE_DIR}/test_dir"
scp -i ${SSH_KEY} -o StrictHostKeyChecking=no -P 22 -r "${WORKSPACE}/h5_vip/test_dir" root@${VIP_HOST}:${VIP_REMOTE_DIR}
'''
}
break
case "dev":
echo "Development deployment logic (not implemented in this example)"
break
}
}
}
}
}
post {
success {
echo 'Success: Build and deployment completed.'
}
failure {
echo 'Failure: Build or deployment failed. Check logs for details.'
}
always {
// Clean up workspace if needed
// cleanWs()
}
}
}
Java Environment
To run Java applications with Maven, create a custom Docker image based on openjdk:11
(or your desired Java version) and install Maven.
Here’s the Dockerfile
for your custom Java/Maven image:
# Use the official OpenJDK 11 image as the base
FROM openjdk:11
# Set environment variables for Maven
ENV MAVEN_VERSION=3.9.6 # Update to a recent stable Maven version
ENV MAVEN_HOME=/opt/maven
ENV PATH="${MAVEN_HOME}/bin:${PATH}"
# Install necessary packages for downloading and extracting Maven
# Install `gnupg` for verifying Maven downloads (good practice, though not strictly used in this Dockerfile)
USER root
RUN apt-get update && apt-get install -y \
wget \
tar \
gnupg \
# Clean up apt caches to reduce image size
&& rm -rf /var/lib/apt/lists/*
# Download, extract, and symlink Maven
RUN wget https://dlcdn.apache.org/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz -O /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
&& tar xzf /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz -C /opt \
&& ln -s /opt/apache-maven-${MAVEN_VERSION} ${MAVEN_HOME} \
&& rm /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz
# Create a non-root user 'jenkins' with UID 1000 and GID 1000
# This matches the default Jenkins user UID in the official Jenkins image, which is good for volume permissions.
RUN groupadd -r jenkins --gid 1000 && useradd -r -g jenkins -m -d /home/jenkins --uid 1000 jenkins \
&& mkdir -p /home/jenkins/.m2/repository \
&& chown -R jenkins:jenkins /home/jenkins/.m2
# Switch to the non-root user
USER jenkins
# Verify Maven version
RUN mvn -version
Build your new Java/Maven image. Replace my-openjdk-maven:3.9.6
with your chosen tag.
docker build -t my-openjdk-maven:3.9.6 .
Save the image and transfer it to your server:
docker save -o my-openjdk-maven-3.9.6.tar my-openjdk-maven:3.9.6
scp -P 2222 my-openjdk-maven-3.9.6.tar root@106.55.8.163:/home/docker-images
Load the image on your server:
docker load -i ./my-openjdk-maven-3.9.6.tar
Caching Maven Dependencies
To cache downloaded Maven packages and avoid re-downloading them on every build, you can mount a host directory as the Maven local repository within the container.
First, identify the UID/GID of the jenkins
user inside your Maven Docker image:
docker run --rm my-openjdk-maven:3.9.6 id jenkins
# Expected output: uid=1000(jenkins) gid=1000(jenkins) groups=1000(jenkins)
On your host machine, create a directory for Maven caching and ensure it’s owned by the same UID/GID that the jenkins
user has inside the Docker container (typically 1000:1000 for standard Jenkins/Maven setups):
sudo mkdir -p /opt/jenkins-maven-cache
sudo chown -R 1000:1000 /opt/jenkins-maven-cache
sudo chmod -R 775 /opt/jenkins-maven-cache # Give group write permissions for flexibility
Now, here’s a Pipeline script example that uses the custom Java/Maven Docker image and caches Maven dependencies:
pipeline {
agent {
docker {
image 'my-openjdk-maven:3.9.6'
// Mount the host's Maven repository to the container's Maven home
// Ensure the host path (/opt/jenkins-maven-cache) is correctly owned/permissioned
args '-v /opt/jenkins-maven-cache:/home/jenkins/.m2/repository:rw'
}
}
environment {
// Define environment variables for your project
GIT_URL="http://106.55.8.163:8081/mall/springboot-mall.git" // Use HTTPS if available
GIT_AUTH = "" // Fill with your Jenkins Credentials ID for Git
GIT_BRANCH = "${branch}" // Parameter from Jenkins job
PROJECT_ENV = "${project_env}" // Parameter from Jenkins job (e.g., "pro", "dev")
VIP_HOST = '152.22.3.186' // Your VIP server's IP address
VIP_REMOTE_DIR = "/mnt/mall/admin"
}
stages {
stage('Git Checkout') {
steps {
echo 'Checking out Git repository...'
script {
checkout([
$class: 'GitSCM',
branches: [[name: "${GIT_BRANCH}" ]],
doGenerateSubmoduleConfigurations: false,
extensions: [
// Only pull the latest commit for faster checkouts
[$class: 'CloneOption', depth: 1, shallow: true, noTags: true]
],
submoduleCfg: [],
userRemoteConfigs: [[
credentialsId: "${GIT_AUTH}",
url: "${GIT_URL}"
]]
])
}
}
}
stage('Maven Build') {
steps {
echo 'Starting Maven build...'
// Execute Maven package, skipping tests, and specifying the local repository path
sh "mvn clean package -Dmaven.test.skip=true -Dmaven.repo.local=/home/jenkins/.m2/repository -U"
}
}
stage('Deploy Backend') {
when {
expression {
// Only proceed if the previous stage was successful or skipped
currentBuild.result == null || currentBuild.result == 'SUCCESS'
}
}
steps {
script {
switch (PROJECT_ENV) {
case "pro":
// Use SSH credentials for deployment
withCredentials([sshUserPrivateKey(credentialsId: '', keyFileVariable: 'SSH_KEY')]) {
// Transfer compiled JAR and dependencies, then restart the service
sh '''
scp -i ${SSH_KEY} -o StrictHostKeyChecking=no -P 22 "${WORKSPACE}/admin/target/lib" "root@${VIP_HOST}:${VIP_REMOTE_DIR}"
scp -i ${SSH_KEY} -o StrictHostKeyChecking=no -P 22 "${WORKSPACE}/admin/target/admin-2.3.jar" "root@${VIP_HOST}:${VIP_REMOTE_DIR}"
ssh -i ${SSH_KEY} -o StrictHostKeyChecking=no root@${VIP_HOST} '/mnt/sh/admin-8000.sh restart;'
'''
}
break
case "dev":
echo "Development deployment logic (not implemented in this example)"
break
}
}
}
}
}
post {
success {
echo 'Success: Java build and deployment completed.'
}
failure {
echo 'Failure: Java build or deployment failed. Check logs for details.'
}
always {
// Clean up workspace if needed
// cleanWs()
}
}
}
This comprehensive guide should help you deploy Jenkins with Docker and run your Node.js and Java pipelines efficiently. Remember to replace placeholder values like IP addresses, credentials, and image versions with your actual details.