A comprehensive guide to setting up a complete CI/CD pipeline for Python applications using Jenkins and Docker.
In this tutorial, we will create a comprehensive Continuous Integration and Continuous Deployment (CI/CD) pipeline for a Python application. A well-designed CI/CD pipeline automates the build, test, and deployment processes, enabling developers to deliver code changes more frequently and reliably. We’ll use Jenkins for orchestration, Docker for containerization, and various tools to ensure our Python application meets quality and security standards.
The sample code for this project is available at Python Demo App GitHub Repository.
Before starting this project, make sure you have the following prerequisites:
Before we begin, make note of these customizations you might need to make:
Let’s start by setting up Jenkins on an EC2 instance:
sudo apt update -y
sudo apt install openjdk-11-jdk -y
java -version
sudo wget -O /usr/share/keyrings/jenkins-keyring.asc \
https://pkg.jenkins.io/debian-stable/jenkins.io-2023.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
sudo apt-get update
sudo apt-get install jenkins
Don’t forget to open port 8080 in your security group to access Jenkins.
Access Jenkins in your browser using http://<your-ec2-ip>:8080
Retrieve the initial admin password:
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
Install the suggested plugins when prompted.
Create an admin user with your credentials.
You need to install the following plugins:
To install these plugins:
Install Docker on the Jenkins server:
sudo apt-get update
sudo apt-get install docker.io -y
sudo systemctl start docker
sudo docker run hello-world
sudo systemctl enable docker
docker --version
sudo usermod -a -G docker jenkins
sudo usermod -a -G docker $(whoami)
newgrp docker
sudo systemctl restart jenkins
Install SonarQube using Docker:
docker run -d --name sonarqube -p 9000:9000 sonarqube:lts-community
Access SonarQube at http://<your-ec2-ip>:9000 with the default login credentials:
Generate a token for Jenkins integration:
Configure SonarQube in Jenkins:
Before we can create our CI/CD pipeline, we need to ensure all the necessary tools are available on our Jenkins server. Let’s create a preliminary pipeline to install these tools:
pipeline {
agent any
stages {
stage('Install Python Tools') {
steps {
sh 'sudo apt-get update -y'
sh 'sudo apt-get install python3 -y'
sh 'sudo apt-get install python3-pip -y'
sh 'sudo apt-get install python3-venv -y'
}
}
stage('Verify Docker') {
steps {
sh 'docker --version'
}
}
stage('Install Make') {
steps {
sh 'sudo apt-get install make -y'
sh 'sudo apt-get install make-guile -y'
}
}
}
}
Now we’re ready to create our main CI/CD pipeline for the Python application:
pipeline {
agent any
environment {
DOCKER_HUB_CREDS = credentials('dockerhub')
}
stages {
stage('Checkout Code') {
steps {
git url: 'https://github.com/benc-uk/python-demoapp.git', branch: 'main'
// Optional: Update the Flask version for security
sh '''
sed -i 's/Flask==2.0.1/Flask>=2.2.2/g' requirements.txt
cat requirements.txt
'''
}
}
stage('Setup Python Environment') {
steps {
sh '''
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt
pip install pytest pytest-cov flake8
'''
}
}
stage('Code Quality Analysis') {
steps {
sh '''
. venv/bin/activate
flake8 --max-line-length=120 --exclude=venv .
'''
}
}
stage('Run Tests') {
steps {
sh '''
. venv/bin/activate
pytest --cov=. --cov-report=xml
'''
}
}
stage('Build Docker Image') {
steps {
sh 'make image'
sh 'docker images'
}
}
stage('Security Scan') {
steps {
// Optional: Install Trivy if not already installed
sh '''
if ! command -v trivy &> /dev/null; then
sudo apt-get install wget apt-transport-https gnupg lsb-release -y
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy -y
fi
'''
// Scan the Docker image
sh 'trivy image --severity HIGH,CRITICAL --no-progress your-dockerhub-username/python-demoapp:latest'
}
}
stage('Push Image to Docker Hub') {
steps {
script {
withCredentials([string(credentialsId: 'dockerhub', variable: 'DOCKER_HUB_TOKEN')]) {
sh '''
echo $DOCKER_HUB_TOKEN | docker login -u your-dockerhub-username --password-stdin
make push
'''
}
}
}
}
stage('Deploy Container') {
steps {
sh '''
docker stop python-demoapp || true
docker rm python-demoapp || true
docker run -d --name python-demoapp -p 5000:5000 your-dockerhub-username/python-demoapp:latest
'''
}
}
}
post {
always {
// Clean up workspace
cleanWs()
}
success {
echo 'Pipeline completed successfully!'
}
failure {
echo 'Pipeline failed!'
}
}
}
Make sure to replace your-dockerhub-username with your actual Docker Hub username.
Before running the pipeline, add Docker Hub credentials to Jenkins:
Let’s break down the key components of our pipeline:
Fetches the latest code from the GitHub repository and optionally updates the Flask version for security.
Creates a virtual environment and installs all required dependencies, plus testing tools.
Uses flake8 to check the code against Python style guide recommendations.
Executes pytest with coverage reporting to ensure code quality and functionality.
Uses Make to build a Docker image of the application.
Uses Trivy to scan the Docker image for security vulnerabilities.
Pushes the Docker image to Docker Hub for distribution.
Deploys the application as a Docker container on the Jenkins server.
The Python Demo App repository includes a Makefile to simplify Docker operations. You’ll need to customize it for your own use:
REGISTRY := docker.io
GROUP := your-dockerhub-username
IMAGE := python-demoapp
# Version, update as needed
VERSION := 1.0.0
# Default tag format: registry/group/image:version
TAG := $(REGISTRY)/$(GROUP)/$(IMAGE):$(VERSION)
LATEST := $(REGISTRY)/$(GROUP)/$(IMAGE):latest
.PHONY: help image push
help: ## Show help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
image: ## Build the container image
docker build --file Dockerfile --tag $(TAG) --tag $(LATEST) .
push: ## Push the image to registry
docker push $(TAG)
docker push $(LATEST)
Replace your-dockerhub-username with your Docker Hub username.
After the pipeline completes, verify the deployment by accessing your Python application:
http://<your-ec2-ip>:5000Here are some ways to enhance your Python CI/CD pipeline:
Integrate SonarQube analysis in your pipeline:
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('SonarQube') {
sh '''
. venv/bin/activate
pip install pylint
pylint --output-format=parseable --reports=no --exit-zero src/ > pylint-report.txt
sonar-scanner \
-Dsonar.projectKey=python-demoapp \
-Dsonar.sources=. \
-Dsonar.python.coverage.reportPaths=coverage.xml \
-Dsonar.python.pylint.reportPath=pylint-report.txt
'''
}
}
}
Create a more sophisticated deployment strategy for different environments:
stage('Deploy') {
steps {
script {
def deployEnv = 'dev'
if (env.BRANCH_NAME == 'main') {
deployEnv = 'prod'
} else if (env.BRANCH_NAME == 'staging') {
deployEnv = 'staging'
}
sh "make deploy ENV=${deployEnv}"
}
}
}
Integrate safety to check Python dependencies for security issues:
stage('Check Dependencies') {
steps {
sh '''
. venv/bin/activate
pip install safety
safety check -r requirements.txt --full-report
'''
}
}
Add functionality to roll back to previous versions if deployment fails:
stage('Deploy with Rollback') {
steps {
script {
try {
sh '''
docker stop python-demoapp || true
docker rm python-demoapp || true
docker run -d --name python-demoapp -p 5000:5000 your-dockerhub-username/python-demoapp:latest
'''
// Verify deployment success
sh '''
sleep 10
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/)
if [ "$response" != "200" ]; then
exit 1
fi
'''
} catch (Exception e) {
echo 'Deployment failed, rolling back...'
sh 'docker run -d --name python-demoapp -p 5000:5000 your-dockerhub-username/python-demoapp:previous'
error 'Deployment failed, rolled back to previous version'
}
}
}
}
Here are some best practices to follow when building CI/CD pipelines for Python applications:
Always use virtual environments to isolate dependencies and prevent conflicts.
Use pip freeze > requirements.txt to create reproducible builds.
Include unit tests, integration tests, and end-to-end tests in your pipeline.
Use tools like flake8, pylint, or black to ensure code quality and consistency.
Regularly scan dependencies and container images for vulnerabilities.
Use semantic versioning for your Docker images and releases.
For zero-downtime deployments, implement blue/green or canary deployment strategies.
If Jenkins can’t access Docker, ensure the Jenkins user is in the Docker group:
sudo usermod -a -G docker jenkins
sudo systemctl restart jenkins
If you encounter issues with Python virtual environments, try:
chmod +x venv/bin/activate
If Docker login fails, verify your credentials and check if you have Docker Hub rate limits:
docker login -u your-dockerhub-username
If pytest fails with import errors, ensure all dependencies are installed in the virtual environment:
. venv/bin/activate
pip install -r requirements.txt
pip install pytest pytest-cov
In this tutorial, we’ve created a comprehensive CI/CD pipeline for a Python application using Jenkins and Docker. We’ve covered:
This pipeline provides a solid foundation for automating the delivery of Python applications, ensuring code quality, security, and reliability in the process. As your application grows, you can extend this pipeline with additional stages and integrations to meet your specific requirements.
By implementing a CI/CD pipeline for your Python applications, you’ll reduce manual errors, increase deployment frequency, and provide faster feedback to your development team, ultimately leading to better software quality and happier users.