Building a Docker-Jenkins CI/CD Pipeline for a Python App (Part 2)

nyukeit

Nyukeit

Posted on December 13, 2022

Building a Docker-Jenkins CI/CD Pipeline for a Python App (Part 2)

This is a continuation of the tutorial for building a Docker Jenkins pipeline to deploy a simple Python app using Git and GitHub. The first part of the tutorial can be found here.

Installing Jenkins

We now have the basics ready for deploying our app. Let's install the remaining software to complete our pipeline.

We begin by importing the GPG key which will verify the integrity of the package.

curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo tee \
  /usr/share/keyrings/jenkins-keyring.asc > /dev/null
Enter fullscreen mode Exit fullscreen mode

Next, we add the Jenkins softwarey repository to the sources list and provide the authentication key.

echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
  https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
  /etc/apt/sources.list.d/jenkins.list > /dev/null
Enter fullscreen mode Exit fullscreen mode
sudo apt update
Enter fullscreen mode Exit fullscreen mode

Jenkins Key and Source

Now, we install Jenkins

sudo apt-get install -y jenkins
Enter fullscreen mode Exit fullscreen mode

Wait till the entire installation process is over and you get back control of the terminal.

Installing Jenkins

To verify if Jenkins was installed correctly, we will check if the Jenkins service is running.

sudo systemctl status jenkins.service
Enter fullscreen mode Exit fullscreen mode

Verifying Jenkins Installation

Press Q to regain control.

Jenkins Configuration

We have verified that the Jenkins service is now running. This means we can go ahead and configure it using our browser.

Open your browser and type this in the address bar:

localhost:8080
Enter fullscreen mode Exit fullscreen mode

You should see the Unlock Jenkins page.

Unlocking Jenkins for First Use

Jenkins generated a default password when we installed it. To locate this password we will use the command:

sudo cat /var/lib/jenkins/secrets/initialAdminPassword
Enter fullscreen mode Exit fullscreen mode

Jenkins first unlock password

Copy this password and paste it into the box on the welcome page.

On the next page, select 'Install Suggested plugins'

Jenkins Suggested Plugins

You should see Jenkins installing the plugins.

Jenkins Plugins Installation

Once the installation has completed, click on Continue.

On the Create Admin User page, click 'Skip and Continue as Admin'. You can alternatively create a separate Admin user, but be sure to add it to Docker group.

Click on 'Save and Continue'

On the Instance Configuration page, Jenkins will show the URL where it can be accessed. Leave it and click 'Save and Finish'

Click on 'Start Using Jenkins'. You will land on a welcome page like this:

Jenkins Landing Page

We have now successfully setup Jenkins. Let's go back to the terminal to install Docker.

Installing Docker

First we need to uninstall any previous Docker stuff, if any.

sudo apt-get remove docker docker-engine docker.io containerd runc
Enter fullscreen mode Exit fullscreen mode

Most likely, nothing will be removed since we are working with a fresh install of Ubuntu.

We will use the command line to install Docker.

sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
Enter fullscreen mode Exit fullscreen mode

Docker Pre-requisites

Next, we will add Docker's GPG key, just like we did with Jenkins.

sudo mkdir -p /etc/apt/keyrings
Enter fullscreen mode Exit fullscreen mode
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
Enter fullscreen mode Exit fullscreen mode

Now, we will setup the repository

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Enter fullscreen mode Exit fullscreen mode

Next we will install the Docker Engine.

sudo apt-get update
Enter fullscreen mode Exit fullscreen mode
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Enter fullscreen mode Exit fullscreen mode

Docker Installation

Now verify the installation by typing

docker version
Enter fullscreen mode Exit fullscreen mode

Docker version

Notice that you will get an error for permission denied while connecting to Docker daemon socket. This is because it requires a root user. This means you would need to prefix sudo every time you want to run Docker commands. This is not ideal. We can fix this by making a docker group.

sudo groupadd docker
Enter fullscreen mode Exit fullscreen mode

The docker group may already exist. Now let's add the user to this group.

sudo usermod -aG docker $USER
Enter fullscreen mode Exit fullscreen mode

Apply changes to Unix groups by typing the following:

newgrp docker
Enter fullscreen mode Exit fullscreen mode

Note: If you are following this tutorial on a VM, you may need to restart your instance for changes to take effect.

Let's verify that we can now connect to the Docker Engine.

docker version
Enter fullscreen mode Exit fullscreen mode

Docker Engine Version

As we can see, Docker is now fully functional with a connection to the Docker Engine.

We will now create the Dockerfile that will build the Docker image.

Creating the Dockerfile

Inside your terminal, within your folder, create the Dockerfile using the nano editor.

sudo nano Dockerfile
Enter fullscreen mode Exit fullscreen mode

Type this text inside the editor:

FROM python:3.8
WORKDIR /src
COPY . /src
RUN pip install flask
RUN pip install flask_restful
EXPOSE 3333
ENTRYPOINT ["python"]
CMD ["./src/helloworld.py"]
Enter fullscreen mode Exit fullscreen mode

Building the Docker Image

From the Dockerfile, we will now build a Docker image.

docker build -t helloworldpython .
Enter fullscreen mode Exit fullscreen mode

Building the Docker Image

Now let's create a test container and run it a browser to check if our app is displaying correctly.

docker run -p 3333:3333 helloworldpython
Enter fullscreen mode Exit fullscreen mode

Open your browser and go to


 to see our python app in action.

![Python Webapp Running](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z7kys0xb3jft1nt15wa9.png)

Now let's see how we can automate this printing every time we make a change to our python code.

## Creating the Jenkinsfile

We will create a Jenkinsfile which will elaborate a step-by-step process of building the image from the Dockerfile, pushing it to the registry, pulling it back from the registry and running it as a container.

Every change pushed to the GitHub repository will trigger this chain of events.



```bash
sudo nano Jenkinsfile
Enter fullscreen mode Exit fullscreen mode

In the nano editor, we will use the following code as our Jenkinsfile.

node {
    def application = "pythonapp"
    def dockerhubaccountid = "nyukeit"
    stage('Clone repository') {
        checkout scm
    }

    stage('Build image') {
        app = docker.build("${dockerhubaccountid}/${application}:${BUILD_NUMBER}")
    }

    stage('Push image') {
        withDockerRegistry([ credentialsId: "dockerHub", url: "" ]) {
        app.push()
        app.push("latest")
    }
    }

    stage('Deploy') {
        sh ("docker run -d -p 3333:3333 ${dockerhubaccountid}/${application}:${BUILD_NUMBER}")
    }

    stage('Remove old images') {
        // remove old docker images
        sh("docker rmi ${dockerhubaccountid}/${application}:latest -f")
   }
}
Enter fullscreen mode Exit fullscreen mode

Explaining the Jenkinsfile

Our Jenkins pipeline is divided in 5 stages as you can see from the code.

  • Stage 1 - Clones our Github repo
  • Stage 2 - Builds our Docker image from the Docker File
  • Stage 3 - Pushes the image to Docker Hub
  • Stage 4 - Deploys the image as a container by pulling it from Docker Hub
  • Stage 5 - Removes the old image to stop image pile up.

Now that our Jenkinsfile is ready, let's push all of our source code to GitHub.

Pushing files to GitHub

First, let's check the status of our local repo.

git status
Enter fullscreen mode Exit fullscreen mode

Git not tracking files

As we can see, there are no commits yet and there are untracked files and folders. Let's tell Git to track them so we can push them to our remote repo.

git add *
Enter fullscreen mode Exit fullscreen mode

This will add all the files present in the git scope.

Git is now tracking our files and they are ready to be commit. The commit function pushes the files to the staging area where they will be ready to be pushed.

git commit -m "First push of the python app"
Enter fullscreen mode Exit fullscreen mode

First commit to Git

Now, it's time to push our files.

git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Let's go to our repo on GitHub to verify that our push was successful.

Verifying the first push to GitHub

Creating Jenkins Credentials

In the Jenkins dashboard, go to Manage Jenkins.

Manage Jenkins

In the Security section, go to Manage Credentials.

Manage Credentials in Jenkins

In the credentials section, click on System. On the page that opens, click on Global Credentials Unrestricted

System Credentials

Global Credentials

Now click on Add Credentials.

Keep 'Kind' as 'Username and Password'

In 'username' type your Docker Hub username.

In 'password' type your Docker Hub password.

Note: If you have enabled 2FA in your Docker Hub account, you need to create an access token and use it as a password here.

In 'ID', type 'dockerHub'

Finally, click on Create

Docker credentials in Jenkins

Creating a Jenkins Job

To close our pipeline, we will create a Jenkins job which will be triggered when there are changes to our GitHub repo.

Note: In Jenkins, if not already installed, install the plugins Docker and Docker Pipeline. Restart your Jenkins instance after installation.

Click on New Item in your Jenkins dashboard. Enter any name you like. Select Pipeline and click okay.

Jenkins New Project Pipeline

In the configuration page, type in any description that you want.

Configuring a Jenkins Project

In 'Build Triggers' select Poll SCM.

Jenkins Poll SCM & Schedule

In 'Schedule', type

* * * * *

(with spaces in between. This will poll our GitHub repo every minute to check if there any changes. This is mostly too quick for any project, but we are just testing our code.

In the 'Pipeline' section, in 'definition' select Pipeline Script from SCM. This will look for the Jenkinsfile that we uploaded to our repo in GitHub and apply it.

Next, in SCM in the Repositories section, copy and paste your GitHub repo HTTPS URL.

Jenkins Poll SCM Config

In 'Branches to Build', by default, it will have master. Change it to main, since our branch is called main.

Make sure the 'Script Path' has 'Jenkinsfile' already populated. If not, you can type it out.

Jenkins SCM Branch

Click on Save.

Now our Jenkins job is created. It is time to see the whole pipeline in action.

Click on 'Build Now'. This will trigger all the steps and if we have all the configurations correct, it should have our container running with the python app and our custom image uploaded on Docker Hub. Let's verify this.

Jenkins Console Output

Verifying our image on Docker Hub

As we can see, our custom built image is now available in our Docker Hub account.

Now let's verify if the container is running.

docker ps
Enter fullscreen mode Exit fullscreen mode

Committing changes to Python App

To see the full automated flow in action, let's change the python app a bit and go back to our browser to see the changes being reflected automatically.

We have changed the output text from Hello World! to Hello World! I am learning DevOps!

Save the file and push the file to GitHub.

As we can see, this action triggered an automatic job creation on Jenkins, which resulted in Build No. 2 of our app.

Jenkins build auto-trigger

We can now see that our app has 2 builds. In the first build, we can see 'no changes' because we manually triggered the first build after creating our repository. All subsequent commits will result in a new build.

We can see that Build No 2 mentions there was 1 commit.

Jenkins 2nd Build Successfull

As for our webapp, the message displayed has now changed.

Python App updated

This is how we can create a Docker-Jenkins automation.

Resources

Installing Jenkins

Installing Docker on Ubuntu

Fix Docker Socket Permission Denied

Dockerize your Python Application

Containerize A Python Application

💖 💪 🙅 🚩
nyukeit
Nyukeit

Posted on December 13, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related