Creating a Jenkins CI/CD pipeline
Setting up a Jenkins to build and deploy a node application to Docker Hub
This tutorial was written together with Fredrik Björkman.
Table of Contents
- Creating a simple node project
- Installing Jenkins
- Continuous Integration
- Continuous Deployment
- Testing our final pipeline
So what is Jenkins and why would you want a program like Jenkins in your system? Jenkins allows Continuous Integration (CI) in your system. CI is a development practice that requires developers to integrate code into a shared repository at regular intervals. This leads to developers' frequent builds and the common practice is that, whenever a new code commit occurs, a new build should be triggered.
Jenkins can also integrate tests in the building process, where it will run the test automatically and then deploy the new build if the tests succeed or deny the new commit if the build fails. This is a big and important step in the automation of the software development process and will make it possible to find problems in the build in an early stage with the help of automatic tests.
For more information, check out this article about Jenkins.
This tutorial is based on working on a Ubuntu machine, in case you are using a different OS the overall process will be the same, but some of the specific steps might look different.
For this tutorial you will need the following:
- GitHub account and basic git knowledge
- Docker Hub account
- Java installed
- Socketxp account (can be created during the tutorial)
Creating a simple node project
We will be using a very simple node application to test our CI/CD pipeline for this tutorial. You can either fork the project or even better, use one of your own projects to understand the true benefit of CI/CD. This premade project only consists of a simple web server and a simple test that will let us see easily how Jenkins can be used. The only requirement for the project is that it has a Dockerfile. A Dockerfile is used to specify how to create a Docker Image from our project. We will use the following Dockerfile:
The Dockerfile is simple, it uses the official node image, creates a working directory, installs dependencies, bundles the application, defines what port to listen to, and finally sets the command to run the application.
Now create a GitHub repository for your application and push your code. We will set up Jenkins to listen to this GitHub repository so that for every commit you push to the repo, Jenkins will take the committed code, build the project and finally publish it to Docker Hub. That way, once this pipeline is set up you don’t have to worry about deploying your application anymore and can spend all your energy on building an awesome application.
Jenkins runs using Java, if you haven’t installed java already you can do it by entering the following command in the terminal:
$ sudo apt-get install openjdk-11-jre
We then install Jenkins by typing the following commands into your terminal (That is if you are on a Debian-based OS, otherwise check here to see what commands to run for your OS)
$ wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -$ sudo sh -c 'echo deb https://pkg.jenkins.io/debian binary/ > /etc/apt/sources.list.d/jenkins.list'$ sudo apt-get update$ sudo apt-get install jenkins
Note: One could also run Jenkins inside a docker container for a very simple Jenkins setup that is portable to other machines, you can do that as explained here. However, it is worth noting that this would require running docker in docker as we want Jenkins to create docker images, which might result in having to introduce some additional fixes later.
Now you can run Jenkins using:
$ sudo systemctl start jenkins
To check the status of Jenkins run
$ sudo systemctl status jenkins
By default, Jenkins will run on port 8080. Open up your browser and go to localhost:8080, you should be greeted with a screen like this:
It will ask you for a generated initial password which you can find by typing the following (it might be a different path on your system):
$ cat /var/lib/jenkins/secrets/initialAdminPassword
Copy the password and enter it into Jenkins, you will be able to create an account with an easier password to remember after the next step.
Next, select Install suggested plugins. We will need a couple of plugins for this Jenkins set up but most of them will be installed directly here. You will however need to install two more plugins. When the installation is complete and you are in the Jenkins Dashboard go to Manage Jenkins > Manage Plugins and under the Available tab. Install the following plugins:
- Docker Pipeline
Go back to the dashboard and then to Manage Jenkins > Global Tool Configuration. For both Docker and NodeJS, give it a name and select Install automatically, then select install from nodejs.org and from docker.com. Click Save/Apply.
Done! Jenkins is now installed and ready to be used to automate your development process.
Now let’s look at how we make Jenkins automatically build and test our node application on every commit. (In our simple example we have no code that will be compiled and this step will therefore only test the application, in most projects, however, some sort of build step is also necessary). Since we are running Jenkins locally on our machine, we need some way to expose the local Jenkins machine to our GitHub repository. That way we can set up a webhook in our GitHub repository to notify the Jenkins instance every time a new commit is pushed to the repository, and let that trigger a new build.
To expose our local server to the internet we will be using an application called Ngrok (any service that lets you expose a locally hosted application will work, you can find a comparison of services with pros and cons here), you can create a free account on their website. This will create an HTTPS tunnel between our local server and a public URL that we can access in our webhook. To set it up run the following commands (or follow this tutorial for non-Linux platforms).
We will install Ngrok from the Snap Store. To first enable Snapd, run the following command:
$ sudo apt update$ sudo apt install snapd$ sudo snap install ngrok
When Ngrok is installed, we now need to configure Ngrok with your authentication token to be able to access account-only features. You will only be required to do this once. By logging in to your Ngrok dashboard through their website, you can acquire your authentication token by going to Getting Started > Your Authtoken.
With your authentication token acquired, you can type into the terminal the following command to configure Ngrok:
$ ngrok authtoken <YOUR_AUTHTOKEN>$ ngrok http http://localhost:8080
The last command will generate two public URLs that are now connected to your local Jenkins Server. Choose the HTTPS-link, this is the URL that the GitHub repository will use to notify Jenkins at every commit.
Go to the settings of your GitHub repository and go to Webhooks. Add a new webhook, add the previously generated public URL + “/github-webhook/” to the Payload URL and change the content type to application/json. Select Just the push event to trigger the webhook, and finally click Add webhook.
Now let’s head back to the Jenkins dashboard to finalize the CI pipeline by defining a job that will listen to commits from the repo and build the node application when it happens. Click New item, give it a name (I called mine “continuous-integration”), and then select Freestyle Project. You will be presented with a bunch of option, the following settings should be set:
- Under Source Code Management, select git, then enter the URL of your GitHub repo as well as the branch you would like Jenkins to listen to.
- Under Build Triggers, select GitHub hook trigger for GITScm polling
- Under Build Environment, select Provide Node & npm bin/ folder to PATH. Select your node installation under NodeJS Installation
- Under Build, add the build step Execute Shell and in the prompt enter:
npm installnpm test
If you don’t have any tests you only need to add npm install in this step. However, adding some testing here is highly encouraged. This way you can automatically test your code every time you commit, and prevent the code from deploying if it doesn’t pass your written tests.
Click save and let’s test and see if it works. To test if the configuration is set up correctly, try to push some changes to the GitHub repository. Make sure that in your Ngrok terminal, you see the “200 OK” from the /github-webhook/.
After that, in Jenkins, select your continuous-integration job in the dashboard. In the bottom left corner, you should see a green checkmark. This means that Jenkins has caught the webhook, fetched the new changes and built the project. To see what Jenkins did for you select the build (the “#9” in the image below) and click on Console Output.
Now that we have set up Jenkins to automatically build and test our application we want it to build a docker image of the application and publish it to docker hub. This is an easy way of sharing an application among several platform-independent machines.
Start by creating a new repository on Docker Hub.
To publish an image to your repository you will need to add the credentials of your Docker Hub account to Jenkins, do so by navigating to Manage Jenkins > Manage Credentials > System > Global Credentials > Add Credentials. Add your credentials and give them an id that you will use later.
Now go back to the dashboard and create a new item and select Pipeline.
Under Build Triggers, select Build after other projects are built, enter the name of your previous CI item, and select Trigger only if build is stable. That way we will only deploy the application if the previous tests succeeded.
Next under Pipeline, select Pipeline script and enter the following script. Make sure to change the environment variables at the top of the script. Change registry (repository) and gitUrl/gitBranch to your repositories, and change the registryCredential variable to the id of your Docker Hub credentials that you created earlier.
This script is fairly simple, there are four stages. Firstly we clone our project from GitHub, we then build our Docker image, push it to Docker Hub and finally delete the image to clean up our server memory.
Click Save, and that’s it!
Testing our final pipeline
Let’s try to make another commit and see what happens. After your commit, go to the Jenkins Dashboard and if everything went as planned it should look something like this.
Head to your Docker Hub Repository and check that the changes were pushed to Docker Hub as well.
And there we have it, your Jenkins pipeline is now set up to automatically build and deploy a node application to docker hub, good job! 🎉
This tutorial only showed the tip of the iceberg of possibilities that Jenkins provides. The key is that you play around with the tool and find the settings and plugins that match your development workflow. In the end, it is all about automating repetitive tasks that free up more time to create awesome applications.