Creating Docker Containers using Jenkins

I have an application that I want to run in a Docker container, and I have a CI/CD pipeline setup for that application in Jenkins. What’s the best way to automatically build a Docker container through my CD pipeline?

When I began exploring options, I downloaded a few of the freely available community contributed Jenkins Docker plugins. After some initial testing with those plugins, I decided that it would be best to simply run Jenkins on my Docker host, and to execute Docker CLI commands in a “Execute Shell” build step in my CD pipeline.

My application is a Spring Boot app, built with Gradle, and it depends on a Mongodb database. When I push my changes to Github.com, Github will trigger the commit hook for my CI build, and the Jenkins will run my unit tests. If the CI build passes, then the downstream CD build will kick off where my application’s binary is built, my Docker image is built, and my container is automatically started.

Generically, the sequence looks like this:

git_jenkins_docker_ci_cd_pipeline_sequence

Implementing the above pipeline in Jenkins required a few plugins, some of which are optional depending on the type of application you are building for your own pipeline. For my build, I used the following plugins:

  • Github plugin
  • Gradle plugin
  • Mask Passwords plugin

The Github plugin is required to allow me to checkout my code from Github.com. The Gradle plugin allows me to compile, test, and assemble my application source code. The Mask Passwords plugin is going to be used to inject sensitive information into a later “Execute Shell” build step that launches my Docker container.

It’s worth including a high level system interaction diagram to illustrate the architecture that I’m working with:

malone_cloud_architecture_iteration_2

I have two VMs running on digitalocean.com, one which runs my Docker host and my Jenkins build server, and another which runs my data services, including MySQL and MongoDB. Applications running within containers access those data services, and the information required to access them are injected into them as environment variables.

This helps to simplify things from the Jenkins perspective. I only need to run Docker CLI commands from within the same VM as the Docker host in order to build and run containers. This isn’t exactly ideal for a production server setup, but it will suffice for a basic CI/CD pipeline setup for non production purposes.

For a production pipeline, you would most likely want to build your Docker images and then push them to either Docker Hub, or your own internal Docker image repository. Those topics are outside of the scope of this article, since it was not part of my initial goals for CI/CD with Docker and Jenkins. It’s also worth noting that Docker has recently released a number of tools in Docker Hub to assist with CI/CD for Docker images.

In order to setup this build, I start by creating a new Freestyle Project in Jenkins. In the project configuration, under Source Code Management, I select Git, and enter the URL for my Git repo:

Screen Shot 2015-09-29 at 1.49.48 PM

Under the Build Environment section, I check the “Mask Passwords” checkbox, and I begin inserting the variables that I want to use but mask later in my build:

Screen Shot 2015-09-29 at 1.51.09 PM

 

Under the “Build” section, I want to do two things: build my application binary using my Gradle build script, and build and run my Docker container using shell scripts. My application uses the Gradle wrapper, so I set up the Invoke Gradle Script build step to use it:

Screen Shot 2015-09-29 at 1.53.05 PM

Finally, I setup the Execute Shell build step which will create a Docker image, and then launch the Docker container. As was previously mentioned, the Mask Passwords script is used to inject sensitive information into this step using placeholders:

Screen Shot 2015-09-29 at 1.54.49 PM

Here’s the entire contents of my Execute Shell build step:

sudo docker stop personalfinancier
sudo docker rm personalfinancier
sudo docker build --no-cache=true -t dmalone/personalfinancier .
sudo docker run -it --name personalfinancier -p 8090:8080 -e MONGO_USERNAME=${PF_MONGO_USERNAME} -e MONGO_PASSWORD=${PF_MONGO_PWD} -e MONGO_DATABASE=personalfinancier -e MONGO_HOST=${PF_MONGO_HOST} -e MONGO_PORT=27017 -d dmalone/personalfinancier

The first two commands are necessary because I’m running a named container. I have to first stop the running container, and then remove it before I can build it and run it again using the same name. I haven’t yet figured out if this is avoidable.  The first time your build runs, you’ll actually need to comment those first two commands out of the Execute Shell script. Then, for each subsequent builds the entire script should run without issues.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.