Introduction
Jenkins has remained one of the most powerful and flexible continuous integration servers despite the emergence of many cloud-based alternatives. For Flask application development, Jenkins offers distinct advantages: complete control over the execution environment, unlimited build minutes, and extensive customisation options through its plugin ecosystem.
This guide focuses on setting up Jenkins specifically for Flask applications and integrating the pytest ecosystem we discussed in our previous articles. We'll cover everything from initial Jenkins configuration to creating sophisticated pipelines that automate testing, quality checks, and deployment processes.
By the end of this article, you'll understand how to:
- Set up a Jenkins server optimised for Python and Flask projects
- Configure Jenkins pipelines using both Declarative and Scripted syntax
- Integrate pytest, pytest-flask, pytest-cov and other testing tools
- Visualise test results and coverage reports
- Automate quality checks and deployment processes
- Optimise Jenkins for better performance with Flask applications
Setting up Jenkins for Flask applications
Before diving into pipeline configurations, let's establish a proper Jenkins environment for Flask projects.
Installation and basic setup
The most straightforward way to get started with Jenkins is using Docker:
docker run -d -p 8080:8080 -p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
--name jenkins jenkins/jenkins:lts
After installation, access Jenkins at http://localhost:8080 and follow the setup wizard.
Installing essential plugins
For Flask projects, install these crucial plugins:
- Pipeline: Enables pipeline-as-code functionality
- Git Integration: For source code management
- Docker: For containerised builds
- Python: For improved Python support
- Cobertura: For code coverage reports
- JUnit: For test results visualisation
- Blue Ocean: For improved pipeline visualisation
Install these plugins via Manage Jenkins > Manage Plugins > Available.
Creating Python environments
You have two main options for Python environments in Jenkins:
Option 1: System Python with virtualenv
Configure a tool installation in Manage Jenkins > Global Tool Configuration:
// Jenkinsfile example using system Python
pipeline {
agent any
tools {
python 'python3.9'
}
stages {
stage('Setup') {
steps {
sh '''
python -m venv venv
. venv/bin/activate
pip install -r requirements.txt
pip install -r requirements-dev.txt
'''
}
}
}
}
Option 2: Docker-based Python (recommended)
Use Docker containers for consistent environments:
// Jenkinsfile example using Docker
pipeline {
agent {
docker {
image 'python:3.9-slim'
}
}
stages {
stage('Setup') {
steps {
sh 'pip install -r requirements.txt -r requirements-dev.txt'
}
}
}
}
Setting up credentials
Store sensitive information like deployment keys in Jenkins credentials:
-
Navigate to Manage Jenkins > Manage Credentials
-
Add credentials for:
- Git repositories
- Deployment servers
- Docker registries
- API keys
Creating Jenkins pipelines for Flask applications
Jenkins pipelines can be defined using either Declarative or Scripted syntax. Declarative is generally easier for beginners, while Scripted offers more flexibility.
Basic pipeline structure
Here's a complete Declarative pipeline for a Flask application:
pipeline {
agent {
docker {
image 'python:3.9-slim'
}
}
stages {
stage('Setup') {
steps {
sh 'pip install -r requirements.txt -r requirements-dev.txt'
}
}
stage('Linting') {
steps {
sh 'flake8 myapp tests'
sh 'black --check myapp tests'
}
}
stage('Test') {
steps {
sh 'pytest --junitxml=test-results.xml --cov=myapp --cov-report=xml'
}
post {
always {
junit 'test-results.xml'
cobertura coberturaReportFile: 'coverage.xml'
}
}
}
stage('Build') {
steps {
sh 'python setup.py sdist bdist_wheel'
archiveArtifacts artifacts: 'dist/*', fingerprint: true
}
}
}
post {
always {
cleanWs()
}
}
}
Save this as Jenkinsfile in your repository root.
Integrating pytest tools with Jenkins
Let's see how to integrate the pytest tools we discussed in previous articles.
pytest-flask integration
stage('Test') {
steps {
sh '''
pytest --junitxml=test-results.xml \
--cov=myapp \
--cov-report=xml \
--cov-report=html
'''
}
post {
always {
junit 'test-results.xml'
cobertura coberturaReportFile: 'coverage.xml'
publishHTML target: [
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'htmlcov',
reportFiles: 'index.html',
reportName: 'Coverage Report'
]
}
}
}
pytest-cov for code coverage
Jenkins can visualise coverage reports with the Cobertura plugin:
stage('Coverage') {
steps {
sh 'pytest --cov=myapp --cov-report=xml --cov-fail-under=85'
}
post {
always {
cobertura coberturaReportFile: 'coverage.xml',
conditionalCoverageTargets: '70, 0, 0',
lineCoverageTargets: '85, 0, 0',
methodCoverageTargets: '80, 0, 0',
failNoReports: true,
failUnhealthy: true,
failUnstable: false
}
}
}
pytest-mock integration
stage('Unit Tests') {
steps {
sh 'pytest tests/unit --junitxml=unit-test-results.xml'
}
post {
always {
junit 'unit-test-results.xml'
}
}
}
Handling flaky tests with pytest-rerunfailures
stage('Integration Tests') {
steps {
sh 'pytest tests/integration --reruns 3 --reruns-delay 1 --junitxml=integration-test-results.xml'
}
post {
always {
junit 'integration-test-results.xml'
}
}
}
Parallel testing strategies
Speed up your pipeline with parallel testing:
stage('Test') {
parallel {
stage('Unit Tests') {
steps {
sh 'pytest tests/unit --junitxml=unit-test-results.xml'
}
post {
always {
junit 'unit-test-results.xml'
}
}
}
stage('Integration Tests') {
steps {
sh 'pytest tests/integration --junitxml=integration-test-results.xml'
}
post {
always {
junit 'integration-test-results.xml'
}
}
}
stage('Functional Tests') {
steps {
sh 'pytest tests/functional --junitxml=functional-test-results.xml'
}
post {
always {
junit 'functional-test-results.xml'
}
}
}
}
}
Advanced Jenkins pipeline techniques
Multi-branch pipelines
Multi-branch pipelines automatically discover branches and pull requests in your repository:
- In Jenkins, select New Item
- Choose Multibranch Pipeline
- Configure the branch source (e.g., GitHub)
- Set the build configuration path to
Jenkinsfile
This will create pipelines for all branches that contain a Jenkinsfile.
Environment-specific pipelines
Configure different behaviours based on the branch:
pipeline {
agent {
docker {
image 'python:3.9-slim'
}
}
environment {
FLASK_ENV = "${env.BRANCH_NAME == 'main' ? 'production' : 'development'}"
}
stages {
stage('Test') {
steps {
sh 'pytest'
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
echo 'Deploying to staging...'
// Deployment steps here
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
steps {
echo 'Deploying to production...'
// Production deployment steps here
}
}
}
}
Shared libraries
For organisations with multiple Flask projects, create reusable pipeline components:
- Create a Git repository for shared Jenkins libraries
- In Manage Jenkins > Configure System, add the repository under Global Pipeline Libraries
- Create helper functions in your library:
// vars/flaskPipeline.groovy
def runTests(Map config = [:]) {
def testPath = config.testPath ?: 'tests'
def coverageThreshold = config.coverageThreshold ?: 80
sh """
pytest ${testPath} \
--junitxml=test-results.xml \
--cov=myapp \
--cov-report=xml \
--cov-fail-under=${coverageThreshold}
"""
junit 'test-results.xml'
cobertura coberturaReportFile: 'coverage.xml'
}
- Use the library in your Jenkinsfile:
@Library('my-jenkins-library') _
pipeline {
agent {
docker {
image 'python:3.9-slim'
}
}
stages {
stage('Setup') {
steps {
sh 'pip install -r requirements.txt -r requirements-dev.txt'
}
}
stage('Test') {
steps {
flaskPipeline.runTests(
testPath: 'tests/unit',
coverageThreshold: 85
)
}
}
}
}
Flask-specific pipeline configurations
Testing with different database backends
pipeline {
agent {
docker {
image 'python:3.9-slim'
}
}
stages {
stage('Test with SQLite') {
steps {
sh '''
export DATABASE_URL="sqlite:///test.db"
pytest tests/
'''
}
}
stage('Test with PostgreSQL') {
agent {
docker {
image 'python:3.9-slim'
args '-u root --network jenkins-network'
}
}
steps {
sh '''
export DATABASE_URL="postgresql://postgres:postgres@postgres:5432/testdb"
pytest tests/
'''
}
}
}
}
This requires setting up a Docker network:
docker network create jenkins-network
docker run -d --name postgres --network jenkins-network -e POSTGRES_PASSWORD=postgres postgres:13
Testing with browser automation
For Flask applications with frontend components:
pipeline {
agent {
docker {
image 'python:3.9-slim'
args '--network jenkins-network'
}
}
stages {
stage('Setup') {
steps {
sh 'pip install -r requirements.txt -r requirements-dev.txt'
}
}
stage('Unit Tests') {
steps {
sh 'pytest tests/unit --junitxml=unit-test-results.xml'
}
post {
always {
junit 'unit-test-results.xml'
}
}
}
stage('Browser Tests') {
steps {
sh '''
export SELENIUM_REMOTE_URL=http://selenium:4444/wd/hub
pytest tests/browser --junitxml=browser-test-results.xml
'''
}
post {
always {
junit 'browser-test-results.xml'
}
}
}
}
}
This requires setting up a Selenium container:
docker run -d --name selenium --network jenkins-network selenium/standalone-chrome
Performance optimisation
Caching dependencies
Use the Stash/Unstash functionality to cache dependencies:
pipeline {
agent {
docker {
image 'python:3.9-slim'
}
}
stages {
stage('Setup') {
steps {
sh '''
pip install -r requirements.txt -r requirements-dev.txt
mkdir -p .pip-cache
pip wheel -r requirements.txt -r requirements-dev.txt -w .pip-cache
'''
stash includes: '.pip-cache/**', name: 'pip-cache'
}
}
stage('Test') {
steps {
unstash 'pip-cache'
sh '''
pip install --no-index --find-links=.pip-cache -r requirements.txt -r requirements-dev.txt
pytest
'''
}
}
}
}
Using Docker caching
For Docker-based builds, leverage layer caching:
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'docker build --cache-from myapp:latest -t myapp:$BUILD_ID .'
}
}
}
}
This requires setting up a proper Dockerfile for your Flask application:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV FLASK_APP=myapp
CMD ["gunicorn", "myapp:create_app()"]
Best practices for Jenkins pipelines with Flask
1. Fail fast with quick checks
Run fast checks early to provide immediate feedback:
stage('Quick Check') {
steps {
sh 'flake8 myapp tests'
sh 'black --check --diff myapp tests'
sh 'pytest tests/unit -xvs'
}
}
2. Implement quality gates
Add quality gates that prevent progression if not met:
stage('Quality Gate') {
steps {
sh 'pytest --cov=myapp --cov-fail-under=85'
}
}
3. Archive artifacts strategically
Only archive artifacts that you'll actually need later:
stage('Build') {
steps {
sh 'python setup.py sdist bdist_wheel'
archiveArtifacts artifacts: 'dist/*.whl', fingerprint: true
}
}
4. Use declarative pipelines where possible
Declarative pipelines are more maintainable and readable than scripted ones.
5. Document pipeline requirements
Add comments to document dependencies and assumptions:
/*
* This pipeline requires:
* - Docker network: jenkins-network
* - PostgreSQL container: postgres:13
* - Environment variables in Jenkins credentials:
* - DEPLOY_KEY
* - PRODUCTION_SERVER
*/
Conclusion
Setting up Jenkins CI pipelines for Flask applications provides a powerful foundation for automating testing, quality checks, and deployment processes. By integrating pytest and its ecosystem of plugins, you can create comprehensive testing workflows that catch issues early and maintain high code quality.
Remember these key points:
- Jenkins offers flexibility and control for Flask application testing
- Docker-based agents provide consistent environments
- Pytest integration enables comprehensive testing
- Parallel testing and caching optimise pipeline performance
- Shared libraries allow reuse across multiple Flask projects
With these techniques, you can build a robust CI system that scales with your Flask application development needs, ensuring quality while accelerating your development workflow.