Automating Django Application deployment (uWSGI and nginx) with Jenkins Declarative Pipeline
In this article, I will show how to automate deployments for your Django application with Jenkins Pipeline. Get ready to focus on building awesome features while Jenkins handles the deployment grunt work.
Prerequisites
Before getting started, you’ll need to have the following:
- A running Jenkins server
- Basic understanding of docker, Jenkins Pipeline and Django application.
If you’re using Amazon EC2, keep the minimum configuration to this for better performance:
Security Groups:
Making Code Ready for deployment
Let’s understand how the application is structured. First, clone the code from this repository in your system:
git clone https://github.com/dhanbdrkarki1/mathbuddy-django-jenkins.git && cd mathbuddy-django-jenkins
The application is setup for two environment: local and prod.
base.py — common settings for the application (same as settings.py)
local.py — Settings for local environment
prod.py — Settings for production environment (current goal of this article)
Also below line is updated in the base.py.
BASE_DIR = Path(__file__).resolve().parent.parent.parent
Note: when you try to run the application in your local machine, you need to indicate the settings module like this:
python manage.py runserver --settings=mathbuddy.settings.local
Containerizing Application
- Dockerfile: Sets up a Python development environment, installs required system libraries and dependencies, and copies the application code.
- docker-compose.yaml: Defines three services: a database service (db), a web application service (mathbuddy-web), and a reverse proxy service (nginx). The web application relies on the database and will wait for it to be available before starting. The reverse proxy will route traffic on port 80 to the web application running on port 8000.
You can also look through config folder where nginx template and uwsgi settings is set up. Nginx and uWSGI are a powerful combination for deploying Django applications where Nginx acts as a reverse proxy for Django requests, forwarding them to uWSGI worker processes that handle the application logic.
Setting up Docker and Sonarqube in the Jenkins Server
SS into the Jenkins server you’re running. If you don’t have Jenkins installed, you can follow this official documentation. Then, install Docker in the Jenkins Server.
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
# add user to the docker group (required)
sudo usermod -aG docker $USER
sudo usermod -aG docker jenkins
newgrp docker
# check docker installation
docker –version
We’ll be running Sonarqube community edition with PostgreSQL database setup. In the home directory of the Jenkins server terminal, create dockerfile using ‘nano Dockerfile’ and paste the following in the nano editor:
FROM sonarqube:lts-community
USER root
RUN apt-get update && apt-get install nodejs npm -y
USER sonarqube
Save it. Again, create another file using: ‘nano docker-compose.yaml’ and paste the following content:
services:
sonarqube:
build: .
platform: linux/x86_64
depends_on:
- sonar_db
environment:
SONAR_JDBC_URL: jdbc:postgresql://sonar_db:5432/sonar
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: sonar
volumes:
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_logs:/opt/sonarqube/logs
ports:
- "9000:9000"
sonar_db:
image: postgres:12
platform: linux/x86_64
environment:
POSTGRES_USER: sonar
POSTGRES_PASSWORD: sonar
volumes:
- postgresql:/var/lib/postgresql
- postgresql_data:/var/lib/postgresql/data
volumes:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
postgresql:
postgresql_data:
Save it and run the docker composer command.
docker compose up -d --build
Now, browse http:<jenkins-server-ip-address>:9000. Login to Sonarqube with default user name (admin) and password (admin) and then update your password. Then, sonarqube is successfully setup.
Setting Environment for Pipeline Script
Installing Plugins
Navigate to Manage Jenkins > Available Plugins and install these plugins.
- Eclipse Temurin installer and OpenJDK-native-plugin (for installing java jdk — no need if already have)
- Sonarqube Scanner (for static code analysis)
- OWASP Dependency-Check (for detecting vulnerabilities)
- Sonarqube Scanner (for static code analysis with SonarQube)
- docker, docker commons, docker pipeline plugins (for docker)
Add Credentials
To generate user token(or secret text) for Sonarqube access, browse http:<jenkins-server-ip-address>:9000. Go to User > My Account > Security and generate token.
Go to Manage Jenkins > Credentials > global > Add Credentials. Copy and paste token in the above secret field.
We will be pushing docker image in the docker hub repository. Create account if you don’t have. Add another credentials in Jenkins.
Next, we’ll need NVD api key for OWASP Dependency check. You can request it by navigating to this api key link. Otherwise, it will show warning message in the console output while running the pipeline.
Copy and paste key in the secret field.
Configuring Tools
Be careful with the name of tools as it will be used later in the pipeline. Go to Manage Jenkins > Tools.
Click on save.
Configuring Global Settings
Goto Manage Jenkins > Global.
Jenkins Pipeline Script Used in the Code
pipeline {
agent any
environment {
SONAR_HOME = tool 'sonar-scanner'
// Sonarqube project key
SONAR_PROJECT_KEY = 'mathbuddy-django'
//for creating django superuser
DJANGO_ADMIN_USERNAME = 'admin'
DJANGO_ADMIN_EMAIL = 'admin@gmail.com'
DJANGO_ADMIN_PASSWORD = 'P@ssword0'
//NVD API KEY for owasp dependency check
NVD_API_KEY = credentials('nvd-api-key')
}
stages {
stage('Checkout Code') {
steps {
git branch: 'main', url: 'https://github.com/dhanbdrkarki1/mathbuddy-django-jenkins.git'
}
}
// Installing all the required dependencies to run the application.
stage('Install Dependencies') {
steps {
sh 'sudo apt-get update -y'
sh 'sudo apt-get install -y python3-pip python3-venv python3-dev'
sh 'python3 -m venv venv'
withEnv(['PATH+VENVS=venv/bin']) {
sh 'pip install -r requirements.txt'
}
}
}
//runs a SonarQube scan on the project's source code to check for code quality and vulnerabilities, excluding specified directories and files, and logs the results.
stage('Sonarqube analysis') {
steps {
script {
withSonarQubeEnv('sonarqube') {
sh """
${SONAR_HOME}/bin/sonar-scanner \
-Dsonar.projectName=${SONAR_PROJECT_KEY} \
-Dsonar.projectKey=${SONAR_PROJECT_KEY} \
-Dsonar.sources=. \
-Dsonar.exclusions=**/migrations/**,**/tests/**,**/venv/**,**/requirements.txt,**/manage.py,**/asgi.py,**/wsgi.py,**/__pycache__/**,**/node_modules/**,**/media/**,**/admin/**,**/jet/**,**/range_filter/**,**/rest_framework/** \
-Dsonar.python.version=3
"""
}
}
}
post {
success {
echo 'SonarQube analysis successful!'
}
failure {
echo 'SonarQube analysis failed, but continuing pipeline...'
}
}
}
// scan all project dependencies for known vulnerabilities and outputs the report in the current directory (./),
// scans the current directory (./) for dependencies, analyzes all dependency types (-f 'ALL'), and leverages the NVD_API_KEY environment variable for vulnerability data.
stage("OWASP SCAN"){
steps{
dependencyCheck additionalArguments: '''
-o './'
-s './'
-f 'ALL'
--nvdApiKey $NVD_API_KEY''', odcInstallation:'owasp-dependency-check'
dependencyCheckPublisher pattern: '**/dependency-check-report.xml'
}
}
// builds a Docker image from the current project, tags it, and then pushes this image to the Docker registry using specified credentials.
stage('Build & Push Docker Image'){
steps{
script{
withDockerRegistry(credentialsId: 'docker', toolName: 'docker') {
sh "docker build -t mathbuddy:latest ."
sh "docker tag mathbuddy:latest dhan007/mathbuddy:latest"
sh "docker push dhan007/mathbuddy:latest"
}
}
}
}
// starts the Docker containers defined in the Docker Compose file in detached mode, runs database migrations, and collects static files for the Django application.
stage('Deploy'){
steps{
script{
withDockerRegistry(credentialsId: 'docker', toolName: 'docker') {
sh "docker compose up -d"
//since custom user is used, user needs to created first hence base app should be defined.
sh 'docker compose exec mathbuddy-web python /code/manage.py makemigrations base'
sh 'docker compose exec mathbuddy-web python /code/manage.py migrate'
sh 'docker compose exec mathbuddy-web python /code/manage.py collectstatic --noinput'
}
}
}
}
// checks if a superuser with the specified username already exists, and if not, it creates the superuser non-interactively and provides the admin login URL.
stage('Create Superuser') {
steps {
script {
def hasAdminUser = sh(returnStdout: true, script: 'docker compose exec mathbuddy-web python /code/manage.py shell -c "from base.models import User; print(User.objects.filter(username=\'$DJANGO_ADMIN_USERNAME\').exists())"').trim()
if (!hasAdminUser.equalsIgnoreCase("True")) {
def loginUrl = "$HTTP_HOST/admin/login/"
sh "echo 'from base.models import User; User.objects.create_superuser(username=\"$DJANGO_ADMIN_USERNAME\", email=\"$DJANGO_ADMIN_EMAIL\", password=\"$DJANGO_ADMIN_PASSWORD\")' | docker compose exec -T mathbuddy-web python /code/manage.py shell"
echo "Superuser created! You can login to admin panel at: ${loginUrl}"
} else {
echo "Superuser already exists. Skipping creation."
}
}
}
}
}
}
Configuring for Pipeline Execution
Save it. After clicking on Build now and after complete execution of pipeline, you can see interface like this.
Click on Passed button under SonarQube Quality Gate above to see the sonarqube analysis like this:
If you click on Latest Dependency Check Link above, you can see results like this:
Accessing Application
You can see the application running on: http:<jenkins-server-ip-address>.
Also, You can login to admin page at http://<jenkins-server-ip-address>/admin/ using credentials; admin@gmail.com and P@ssword.
Try, the application by adding one topic ‘Attendance’ (kind of required).