Building with Docker in Jenkins Pipelines

Diagram of Jenkins and Docker
In this blog, I will demonstrate a straightforward way of building an image and pushing it to Docker Hub using a Jenkinsfile.

First Thing’s First

If you wish to follow along with this exercise, you’ll need a couple things. The good news is that it’s all free.

When installing Docker, make sure to use a Stable release as opposed to an Edge release, or some functionality found in this blog may not work. Note that this blog is intended for people that are familiar with the basics of Git, Docker, and Jenkins.

Preparing the Application and Spinning Up Jenkins

  1. First, make sure you are logged in to GitHub in any web browser. Then, fork the Spring PetClinic repository. This is the example application we will use. It should be simple to swap out Spring PetClinic for your own application if you want more of a challenge.
    Fork Spring-PetClinic
  2. Clone your fork locally. Be sure to replace shanemacbride with your own GitHub username. We will do all work within this directory, so cd into it as well.
    $ git clone https://github.com/shanemacbride/spring-petclinic.git
    $ cd spring-petclinic
  3. Start up Docker. Our Jenkins container will make use of it.
  4. We will be using Liatrio’s Alpine-Jenkins image which is specifically configured for using Docker in pipelines. To spin up the Alpine-Jenkins container and give it access to Docker, use docker run. If you are interested in how the image is configured, be sure to look at the liatrio/alpine-jenkins repository’s Dockerfile for an overview.
    $ docker run -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock liatrio/alpine-jenkins
  5. After waiting for the image to download and run, Jenkins should be visible in a web browser at localhost:8080.
    Jenkins Home Page

Creating a Basic Pipeline Job

  1. Click a new Pipeline job in Jenkins by clicking New Item, naming it, and selecting Pipeline.
    Create a Job in Jenkins
  2. Configure the pipeline to refer to GitHub for source control management by selecting Pipeline script from SCM. Set the repository URL to your fork of Spring PetClinic. The URL I’ve entered here is https://github.com/shanemacbride/spring-petclinic.git.
    Job Configuration
  3. Save the job.

Creating a Dockerfile that Runs our Java Application

  1. We are going to create a Dockerfile that will run the Jar generated by Spring PetClinic building. Create a file named Dockerfile using your favorite text editor. We want to start with a Java image, so specify Anapsix’s Alpine Java image as our base image.
    FROM anapsix/alpine-java
  2. We also should specify who the maintainer of this image should be. This is done using a maintainer label.
    LABEL maintainer="shanem@liatrio.com"
  3. The image needs to have the Spring PetClinic on it so it can be ran. When Spring PetClinic is built, the Jar will be placed in a target directory. We simply need to copy that into the image.
    COPY /target/spring-petclinic-1.5.1.jar /home/spring-petclinic-1.5.1.jar
  4. Finally, we can run Spring PetClinic when the container starts up.
    FROM anapsix/alpine-java
    LABEL maintainer="shanem@liatrio.com"
    COPY /target/spring-petclinic-1.5.1.jar /home/spring-petclinic-1.5.1.jar
    CMD ["java","-jar","/home/spring-petclinic-1.5.1.jar"]
  5. Go ahead and commit this new file. We aren’t pushing any changes yet, because we still need to create a Jenkinsfile for the Pipeline job to execute correctly.
    $ git add Dockerfile
    $ git commit -m 'Created Dockerfile'

Creating a Basic Jenkinsfile

  1. We need to create a Jenkinsfile to instruct our Pipeline job on what needs to be done. First, create the file named Jenkinsfile and specify the first stage. In this stage, we are telling Jenkins to use a Maven image, specifically version 3.5.0, to build Spring PetClinic. After this stage is complete, it will have generated a jar and placed it in the target directory.
    #!groovy
    
    pipeline {
      agent none
      stages {
        stage('Maven Install') {
          agent {
            docker {
              image 'maven:3.5.0'
            }
          }
          steps {
            sh 'mvn clean install'
          }
        } 
      }
    }
  2. Go ahead and run our Pipeline job created before. Make sure to push the Jenkinsfile up to GitHub beforehand. You can run the job by clicking on the clock icon to the right. It should successfully install Spring PetClinic using the Maven image.
    $ git add Jenkinsfile
    $ git commit -m 'Created Jenkinsfile with Maven Install Stage'
    $ git push

Build Job

Success

Adding a Docker Build Stage to the Jenkinsfile

  1. Now that we’ve confirmed Spring PetClinic to be successfully installing, we’re going to package our application inside an image using the Dockerfile created previously. It’s time for another Jenkinsfile stage. In this stage, we’re not going to require a specific Docker image to be used, so any agent will do. The image will be built using the Dockerfile in the current directory, and it will be tagged with my Docker Hub username and repository as the latest image.
    #!groovy
    
    pipeline {
      agent none
      stages {
        stage('Maven Install') {
          agent {
            docker {
              image 'maven:3.5.0'
            }
          }
          steps {
            sh 'mvn clean install'
          }
        }
        stage('Docker Build') {
          agent any
          steps {
            sh 'docker build -t shanem/spring-petclinic:latest .'
          }
        }
      }
    }
  2. If the updated Jenkinsfile is pushed up to GitHub and the job is ran again, it should have successfully built the image. You can verify this by either looking at the job’s console output or examining your images through the Docker CLI.
    $ git add Jenkinsfile
    $ git commit -m 'Added Docker Build Stage'
    $ git push
    $ # Run the Jenkins job which will execute this new stage and wait for it to finish...
    $ docker images
    REPOSITORY                TAG   IMAGE ID        CREATED          SIZE 
    shanem/spring-petclinic latest ef41393a932d 28 seconds ago 160MB
  3. Let’s verify that our Dockerfile was working as expected now that we’ve built our image. We can do this by running our new image with port 8080, the port that the Java servlet runs on, forwarded to port 8081. We do this because our Alpine-Jenkins container is already running on port 8080. After it spins up, we should be able to see Spring PetClinic in a web browser at localhost:8081. Awesome!
    $ docker run -p 8081:8080 shanem/spring-petclinic

Spring PetClinic

Adding Docker Hub Credentials to Jenkins

  1. Now that we have our application successfully installing and packaging itself into a Docker image, we need to make that image available using Docker Hub. First, add your Docker Hub credentials into Jenkins. To do this, first click on Credentials from the Jenkins home page.
    Credentials
  2. Under the global drop down menu, click Add credentials.Add Credentials
  3. Enter your Docker Hub credentials. Make sure to use only your Docker Hub username and not your email address. These credentials will be referenced in the Jenkinsfile using their ID value. Hit OK.
    Docker Hub Credentials
    Credentials Saved

Adding a Docker Push Stage to the Jenkinsfile

  1. Finally, the last stage will be added to our Jenkinsfile that pushes our image up to Docker Hub. Create this stage using any agent, because we don’t need to run our Docker CLI commands in a specific image. Using withCredentials, we can specify to use our Docker Hub credentials defined within Jenkins to login to Docker Hub via the Docker CLI and push our newly built image up.
    #!groovy
    
    pipeline {
      agent none
      stages {
        stage('Maven Install') {
          agent {
            docker {
              image 'maven:3.5.0'
            }
          }
          steps {
            sh 'mvn clean install'
          }
        }
        stage('Docker Build') {
          agent any
          steps {
            sh 'docker build -t shanem/spring-petclinic:latest .'
          }
        }
        stage('Docker Push') {
          agent any
          steps {
            withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
              sh "docker login -u ${env.dockerHubUser} -p ${env.dockerHubPassword}"
              sh 'docker push shanem/spring-petclinic:latest'
            }
          }
        }
      }
    }
  2. Commit these changes, push them up to the GitHub repository, and trigger our Pipeline job to build in Jenkins.
    $ git add Jenkinsfile
    $ git commit -m 'Added Docker Push Stage'
    $ git push
    $ # Run the Jenkins job which will execute this new stage and wait for it to finish...
  3. After the job finishes running, your image should now be on Docker Hub. Great!Job Success

Docker Hub

Next Steps

This is a great introduction to some of the capabilities that Jenkins brings to the table when working with containerized applications. If you want to take things a step further, be sure to check out Liatrio’s blog on Local Development with Jenkins Pipelines.

If you have any comments or questions, reach out to us at @liatrio or shanem@liatrio.

Liatrio is a DevOps consulting firm focussing on helping enterprises get better at software delivery using DevOps & Rapid Release philosophies. We work as “boots on the ground change agents” helping our clients re-imagine their daily work and get better at delivery one day at a time. Liatrio is also hiring! If you enjoy being a part of a team that is solving challenges around software delivery automation, deployment pipelines and large scale transformations, reach out to us via our contact page on our website.

Leave a Reply

Your email address will not be published. Required fields are marked *