Build and Publish Docker Images using Jenkins

Naren Chejara
7 min readJan 5, 2022

--

Building and publishing docker images are frequent actions of dev teams that require code validation and auto-deploy on each commit. In this story, we gonna learn to build and publish docker images.

Prerequisites — before you get to jump into this story, prepare the following in advance

  1. A Jenkins master.
  2. A Docker slave node — make sure the slave node is connected to the master and can able to run the docker commands via Jenkins job.
  3. An Account on DockerHub (Signup for an account on DockerHub — It’s Free!)
  4. A Dockerfile with build steps (You are welcome to use my Demo App)

The first thing you need to do is to make sure your application is ready to build. For this story, I will use a NodeJS application. You can get a Demo application from here if you don’t have

About Demo App

It is a simple CRUD application based on NodeJS and Postgres that manages contacts.

Let’s get started!

Create Jenkins job

Create a Jenkins Pipeline job and configure it to run from the Jenkins file. You can skip the job creation steps if you are familiar with them.

  1. Create ‘my_app’ pipeline job
Create Pipeline job
Create Pipeline job

Install the Pipeline plugin if you cannot see the pipeline job option on the ‘Jenkins new job’ page.

Install the Pipeline plugin
Install the Pipeline plugin

2. Configure Pipeline Script(Jenkinsfile)

Choose the “Pipeline script from SCM” options from the Pipeline section. This option will execute ‘jenkinsfile’ from SCM.

Configure Pipeline script from SCM

Create Jenkins File

Let’s create a ‘jenkinsfile’ file on the root folder of the application, You can also create it on a different location but for this story, I put it on the root folder.

Add the pipeline directive in the Jenkins file.

// Global Variable goes here// Pipeline block
pipeline {
}

Configure the agent.

// Global Variable goes here// Pipeline block
pipeline {
// Agent block
agent { node { label 'Manage_Contact_Demo'}}

}

The above code snippet means the script will execute on the slave nodes who are associated with ‘Manage_Contact_Demo’ label.

Agent directive is allowed to configure the slave node which executes the pipeline script. There are many ways to define agent block such as

Agent blocks example#1
agent any // This will exceute the script any available agent
#2
agent { node { label: 'Label Name' } }
//This will execute on specific machine that associated with the //give label
#3
agent {
docker {
image 'Ubuntu:trusty'
label 'ubuntu_maven'
args '-v /tmp:/home/dev'
}
}
// This will execute script in docker container

Configure Pipeline Job Options

// Global Variable goes here// Pipeline block
pipeline {
// Agent block
agent { node { label 'Manage_Contact_Demo'}}
options {
buildDiscarder(logRotator(numToKeepStr: '30'))
timestamps()
}
}

Options Directive allowed to configure the basic configuration of the pipeline job — You can read to more about options HERE.

Install the Timestamper Plugin if want to use the timestamps option.

Configure Pipeline Job Parameter

// Global Variable goes here// Pipeline block
pipeline {
// Agent block
agent { node { label 'Manage_Contact_Demo'}}
options {
buildDiscarder(logRotator(numToKeepStr: '30'))
timestamps()
}
parameters {
string(
name: "Branch_Name",
defaultValue: 'master',
description: '')
string(
name: "Image_Name",
defaultValue: 'manage_contact_app',
description: '')
string(
name: "Image_Tag",
defaultValue: 'latest',
description: 'Image tag')
string(
name: 'HTTP_PROXY',
defaultValue: '<your proxy>',
description: '')
string(
name: 'HTTPS_PROXY',
defaultValue: '<your proxy>',
description: '')

booleanParam(
name: "PushImage",
defaultValue: false)
}
}

Parameters Directive allowed to configure job parameters in the pipeline.

The HTTP_PROXY parameter can be skipped if it is irrelevant to you.

Configure Docker Image Build Stage

// Global Variable goes here// Pipeline block
pipeline {
// Agent block
agent { node { label 'Manage_Contact_Demo'}}
options {
buildDiscarder(logRotator(numToKeepStr: '30'))
timestamps()
}
....
....

// Stage Block
stages {// stage blocks
stage("Build docker images") {
steps {
script {
echo "Bulding docker images"
def buildArgs = """\
--build-arg HTTP_PROXY=${params.HTTP_PROXY} \
--build-arg HTTPS_PROXY=${params.HTTPS_PROXY} \
-f Dockerfile \
."""
docker.build(
"${params.Image_Name}:${params.Image_Tag}",
buildArgs)
}
}
}
}
}

The above code snippet is using the Docker Pipeline plugin to build the docker image. The plugin has many built-in handy functions that are very helpful for building, pushing docker images. I recommend using Docker Pipeline Plugin but it’s not mandatory you can achieve the same functionality by running Docker commands such as below.

Docker build -t <image_name>:<image_tag> .
// Notice the . (dot) at the end of the command
// . means docker will search and use Dockerfile in the current //directory
// Use -f option for different path
Docker build -t <image_name>:<image_tag> -f path/to/Dockerfile

Note that I used ‘-f Dockerfile .’ that means Docker will look for Dockerfile in the current directiory. This is the default behaviour of the Docmker if you not use ‘-f Dockerfile’ option.

Publish Image on DockerHub

Up to now, we have successfully built the image, it’s time to publish the image to the DockerHub.

In order to push the image, We need to configure the DockerHub credentials in the Jenkins credentials. Let’s add the credentials before adding the push stage in the pipeline.

Go to Manage JenkinsManage CredentialsSystem Add Domain

Add a domain name and press ok.

Jenkins Credential Domain Configuration

Configure the credential

Add your docker hub username and password!

Configure the Push Stage

// Global Variable goes here// Pipeline block
pipeline {
// Agent block
agent { node { label 'Manage_Contact_Demo'}}
options {
buildDiscarder(logRotator(numToKeepStr: '30'))
timestamps()
}
....
....
....

stage("Push to Dockerhub") {
when {
equals
expected: "true",
actual: "${params.PushImage}" }
steps {
script {
echo "Pushing the image to docker hub"
def localImage = "${params.Image_Name}:${params.Image_Tag}"

// pcheajra is my username in the DockerHub
// You can use your username
def repositoryName = "pchejara/${localImage}"

// Create a tag that going to push into DockerHub
sh "docker tag ${localImage} ${repositoryName} "
docker.withRegistry("", "DockerHubCredentials") {
def image = docker.image("${repositoryName}");
image.push()
}
}
}

}
}

The above Code Snippet Push the image to DockerHub. The ‘withRegistry’ function will manage the login session and push the image on DockerHub.

I want to control the push action from the job parameter so I added a When Directive. This is essential when you are planning to do quick sanity before pushing the final image on DockerHub.

That’s it! You did a great job.

Let’s trigger the job. You will see a newly pushed image if everything goes well.

Jenkins job logs

Troubleshoots

You might encounter a permission denied error when running the docker command on the Jenkins user, to fix such issue do the following

  • Run the command
// Adding jenkins user in the docker group
usermod -aG docker jenkins
  • Disconnect and connect the slave node

Here is the complete Jenkins file, You can copy and paste the below contents in the Jenkinfile.

// Jenkinsfile (Declarative Pipeline)// Global Variable goes here// Pipeline block
pipeline {
// Agent block
agent {
node {
label 'Manage_Contact_Demo'
}
}
options {
buildDiscarder(logRotator(numToKeepStr: '5'))
timestamps()
}
parameters {
string(name: "Branch_Name", defaultValue: 'master', description: 'A name of the Git branch that contain the jenkinfile code')
string(name: "Image_Name", defaultValue: 'manage_contact_app', description: 'A name of the image that you want to build') string(name: "Image_Tag", defaultValue: 'latest', description: 'Image tag') string(name: 'HTTP_PROXY', defaultValue: '<add the proxy details if the network is behind the proxy>', description: 'The proxy address to be used to connect to outside network when running docker build.') string(name: 'HTTPS_PROXY', defaultValue: '<add the proxy details if the network is behind the proxy>', description: 'The proxy address to be used to connect to outside network when running docker build.')

booleanParam(name: "PushImage", defaultValue: false)
}
// Stage Block
stages {// stage blocks
stage("Build docker images") {
steps {
script {
echo "Bulding docker images"
def buildArgs = """\
--build-arg HTTP_PROXY=${params.HTTP_PROXY} \
--build-arg HTTPS_PROXY=${params.HTTPS_PROXY} \
-f Dockerfile \
."""
docker.build("${params.Image_Name}:${params.Image_Tag}", buildArgs)
}
}
}
stage("Push to Dockerhub") {
when {
equals expected: "true", actual: "${params.PushImage}"
}
steps {
script {
echo "Pushing the image to docker hub"
def localImage = "${params.Image_Name}:${params.Image_Tag}"
def repositoryName = "pchejara/${localImage}"
sh "docker tag ${localImage} ${repositoryName} "
docker.withRegistry("", "DockerHubCredentials") {
def image = docker.image("${repositoryName}");
image.push()
}
}
}
}}
post {
always {
script {
echo "I am execute always"
}
}
success {
script {
echo "I am execute on success"
}
}
failure {
script {
echo "I am execute on failure"
}
}
}}

Thank You!

--

--