Introduction
Recently, I embarked on setting up a CI/CD pipeline for my Jira clone project. While I was familiar with Azure DevOps and Azure Pipelines, I wanted to explore something new, so I opted for Jenkins running in a Docker container. This post details my journey, including the setup process, challenges I encountered, and how I resolved them.
Project Context
Before diving into the Jenkins setup, here's some context about my project:
- Built a Jira clone with modern web technologies
- Infrastructure provisioned with Terraform
- Wanted a robust CI/CD pipeline for automated testing and deployment
- Needed to work with Docker images as part of the pipeline
Setting Up Jenkins with Docker
1. Running Jenkins in a Docker Container
The first step was to run Jenkins in a Docker container with access to the Docker daemon:
docker run -p 8080:8080 -p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/jenkins:lts
This command:
- Exposes Jenkins on port 8080
- Maps the Jenkins agent port (50000)
- Creates a persistent volume for Jenkins data
- Mounts the Docker socket to allow Docker commands within Jenkins
2. Fixing Docker Socket Permissions
One of the first issues I encountered was permission problems with the Docker socket. To fix this, I ran:
sudo chmod 666 /var/run/docker.sock
3. Retrieving the Jenkins Admin Password
To complete the Jenkins setup, I needed the initial admin password:
docker exec ${CONTAINER_ID} cat /var/jenkins_home/secrets/initialAdminPassword
4. Installing Docker CLI Inside the Jenkins Container
For Docker commands to work inside the Jenkins container, I needed to install the Docker CLI:
# Log in as root
docker exec -it -u root ${CONTAINER_ID} bash
# Install Docker CLI
apt-get update
apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install -y docker-ce-cli
# Add Jenkins user to the docker group
groupadd -f docker
usermod -aG docker jenkins
# Exit container
exit
5. Restarting the Jenkins Container
After making these changes, I restarted the container:
docker restart ${CONTAINER_ID}
Jenkins Configuration
With the container set up, I moved on to configuring Jenkins itself:
6.1 Installing Required Plugins
I installed the following plugins:
- NodeJS Plugin (for my JavaScript-based project)
- Docker Pipeline Plugin (for Docker integration in pipelines)
6.2 Configuring NodeJS
This is where I encountered a significant challenge. Even after installing the NodeJS plugin, I needed to explicitly configure it in the Tools section:
- Go to "Manage Jenkins" > "Tools"
- Enable NodeJS
- Add a NodeJS installation with version 18.17 or higher
- Save the configuration
Important: I discovered that it's not enough to just install the NodeJS plugin. You must also select the NodeJS version in the Tools section, and then explicitly reference this NodeJS installation in your Jenkinsfile with the nodejs
wrapper.
6.3 Setting Up Credentials
For my project, I needed several credentials:
- Go to "Manage Jenkins" > "Manage Credentials"
- Add the following as "Secret text":
-
DATABASE_URL_CREDENTIAL
: PostgreSQL Neon database URL -
CLERK_SECRET_KEY_CREDENTIAL
: Clerk secret key -
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY_CREDENTIAL
: Clerk publishable key -
DOCKER_CREDENTIALS
: Docker Hub credentials (Username with password)
-
Challenges Faced and Solutions
1. Docker Socket Permission Issues in WSL
Since I was using WSL (Windows Subsystem for Linux), I encountered occasional issues with Docker socket permissions. This happened because permissions sometimes reset after system reboots.
Solution: I created a small script to check and fix permissions at startup:
#!/bin/bash
if [ "$(stat -c %a /var/run/docker.sock)" != "666" ]; then
sudo chmod 666 /var/run/docker.sock
echo "Fixed Docker socket permissions"
fi
2. Docker Network Issues with "Docker-in-Docker"
I faced networking challenges when trying to connect containers created by Jenkins to other containers.
Solution: I adopted a "Docker-outside-of-Docker" approach by mounting the host's Docker socket rather than running Docker inside Docker. This way, all containers are created on the host's Docker network.
3. NodeJS Configuration Issues
As mentioned earlier, I discovered that installing the NodeJS plugin wasn't enough. The build would fail with errors about missing Node or npm commands.
Solution:
- Explicitly configure NodeJS in the Tools section
- Reference this specific NodeJS installation in the Jenkinsfile:
pipeline {
agent any
tools {
nodejs 'NodeJS 18.17.0' // Must match the name given in Tools configuration
}
stages {
stage('Build') {
steps {
sh 'node --version'
sh 'npm --version'
sh 'npm install'
// Rest of build steps
}
}
// Other stages
}
}
My Jenkinsfile
Here's a simplified version of the Jenkinsfile I'm using for my Jira clone project:
pipeline {
agent any
tools {
nodejs 'NodeJS 18.17.0'
}
environment {
DOCKER_REGISTRY = 'docker.io'
DOCKER_IMAGE_NAME = 'my-jira-clone'
DOCKER_IMAGE_TAG = "${env.BUILD_NUMBER}"
DATABASE_URL = credentials('DATABASE_URL_CREDENTIAL')
CLERK_SECRET_KEY = credentials('CLERK_SECRET_KEY_CREDENTIAL')
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = credentials('NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY_CREDENTIAL')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install Dependencies') {
steps {
sh 'npm install'
}
}
stage('Run Tests') {
steps {
sh 'npm test'
}
}
stage('Build Docker Image') {
steps {
sh "docker build -t ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_TAG} ."
}
}
stage('Push Docker Image') {
steps {
withCredentials([usernamePassword(credentialsId: 'DOCKER_CREDENTIALS', usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
sh "echo ${DOCKER_PASSWORD} | docker login ${DOCKER_REGISTRY} -u ${DOCKER_USERNAME} --password-stdin"
sh "docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_TAG}"
}
}
}
stage('Deploy') {
steps {
// Deployment steps - in my case, updating Terraform or triggering a deployment script
echo "Deploying version ${DOCKER_IMAGE_TAG}"
// Additional deployment commands
}
}
}
post {
always {
sh 'docker logout ${DOCKER_REGISTRY}'
cleanWs()
}
}
}
Conclusion
Setting up Jenkins with Docker for my Jira clone project was an educational journey. While I encountered several challenges, particularly around Docker permissions, networking, and NodeJS configuration, the solutions I found have resulted in a stable and efficient CI/CD pipeline.
The key takeaways from my experience:
- Always explicitly configure tools in Jenkins, even after installing plugins
- Be aware of permission issues when mounting the Docker socket
- Consider the networking implications when working with Docker in CI/CD
- Document your setup process (as I'm doing here!) to make future maintenance easier
If you're considering Jenkins for your CI/CD needs, especially in a Docker environment, I hope my experience helps you avoid some of the pitfalls I encountered.
Have you set up Jenkins for your projects? What challenges did you face? I'd love to hear about your experiences in the comments!
This post is part of my series on building and deploying a Jira clone application. Check out my profile for more posts in this series.
Top comments (0)