From Code to Container: Building a Python Flask CI/CD Pipeline on Windows with Jenkins



This content originally appeared on DEV Community and was authored by Hritik Raj

Automating the software delivery process is a cornerstone of modern DevOps. Today, I’m sharing my journey of building a complete CI/CD pipeline for a Python Flask application using Jenkins and Docker. This post will walk you through the stages, the code, and—most importantly—the real-world challenges I faced and solved, especially as a Windows user.

If you’ve ever wanted to automate the process of building, testing, and deploying your Python apps, this one’s for you!

The Goal: A Fully Automated Pipeline

The objective was to create a Jenkins pipeline that automatically takes the Python source code from a Git repository and pushes a runnable Docker container to a registry.

Here’s a look at the final pipeline structure in the Jenkins Blue Ocean view. It shows several failed attempts leading to a final, glorious success (Build #9)!

The pipeline consists of six distinct stages:

  1. Clone Repo: Pulls the latest code.
  2. Build: A placeholder stage for build steps.
  3. Setup Environment: Installs Python dependencies.
  4. Build Docker Image: Containerizes the application.
  5. Docker Login & Push: Pushes the image to Docker Hub.
  6. Deploy Application: Runs the container locally.

Dissecting the Jenkinsfile

The entire pipeline is defined as code in a Jenkinsfile. Let’s break it down stage by stage.

Stage 1: Clone Repo

First, we need the source code. This stage uses the git step to clone the main branch from my GitHub repository.

stage('Clone Repo') {
    steps {
        git branch: 'main', 
            url: 'https://github.com/Hritikraj8804/devops-python-app.git'
    }
}

Stage 2: Build

This is a simple placeholder stage. In a more complex project, this is where you would run compilation or transpilation steps. Since Python is an interpreted language, we’ll just echo a message.

Notice the use of bat instead of sh. This was my first “aha!” moment as a Windows user. Jenkins requires you to use bat for Windows Batch commands and sh for Unix/Linux shell commands.

stage('Build') {
    steps {
        bat 'echo Building app on Windows...'
    }
}

Stage 3: Setup Environment

Here, we create a Python virtual environment and install the required packages. This ensures our build environment is clean and has the exact dependencies needed.

stage('Setup Environment') {
    steps {
        bat '''
            python3 -m venv venv
            .
venv/bin/activate
            pip install --upgrade pip
            pip install -r requirements.txt
        '''
    }
}

Learning Moment: The original script line . venv/bin/activate is for Linux/macOS. For a Windows bat step, the correct command to activate the virtual environment is venv\Scripts\activate. This is a classic cross-platform “gotcha”!

Stage 4: Build Docker Image

With our app ready, the next step is to containerize it.This stage executes the docker build command, creating a Docker image tagged as devops-python-app:latest using the Dockerfile in the repository.

stage('Build Docker Image') {
    steps {
        bat 'docker build -t devops-python-app:latest .'
    }
}

Stage 5: Docker Login & Push

This stage was the most challenging. How do you securely log in to Docker Hub from a script? The answer is the Jenkins Credentials Manager.

The withCredentials block securely loads my docker-hub-cred (which I configured in the Jenkins UI) into environment variables (DOCKER_USER and DOCKER_PASS). The script then pipes the password to the docker login command, tags the image with my Docker Hub username, and finally pushes it to the registry.

stage('Docker Login & Push') {
    steps {
        withCredentials([usernamePassword(credentialsId: 'docker-hub-cred',
                                          usernameVariable: 'DOCKER_USER',
                                          passwordVariable: 'DOCKER_PASS')]) {
            bat """
                echo %DOCKER_PASS% | docker login -u %DOCKER_USER% --password-stdin
                docker build -t devops-python-app:latest .
                docker tag devops-python-app:latest %DOCKER_USER%/devops-python-app:latest
        docker push %DOCKER_USER%/devops-python-app:latest
            """
        }
    }
}

Learning Moment: Note the use of %DOCKER_USER% instead of $DOCKER_USER. This is another critical difference between Windows (%VAR%) and Linux ($VAR) syntax for environment variables.

Stage 6: Deploy Application

The final stage deploys our application. It runs the container from the image we just pushed to Docker Hub. The application is run in detached mode (-d), exposing port 5000, and is named devops-python-app. The --rm flag ensures the container is removed when it stops.

stage('Deploy Application') {
    steps {
        withCredentials([usernamePassword(credentialsId: 'docker-hub-cred',
                                          usernameVariable: 'DOCKER_USER',
                                          passwordVariable: 'DOCKER_PASS')]) {
            bat """
            docker run -d -p 5000:5000 --name devops-python-app --rm %DOCKER_USER%/devops-python-app:latest
            """
        }
    }
}

The Sweet Taste of Success

After a few failed runs and some debugging, the pipeline finally turned all green!

Build #9 was manually run by me, HRITIK RAJ, and the entire process from code to a running container took only 54 seconds.

Final Thoughts

This hands-on challenge was an incredible learning experience. The key takeaways for me were:

  • Credentials are King: Never hardcode secrets. Always use a credential manager like the one built into Jenkins.
  • Know Your OS: Scripting for CI/CD is not one-size-fits-all. Understanding the differences between Windows (bat, %VAR%) and Linux (sh, $VAR) is crucial for success.
  • Persistence Pays Off: As you can see from my build history, failure is part of the process. Each red build taught me something new and led to the final green one.

Automating workflows with Jenkins can seem daunting, but by breaking the process down into logical stages and tackling challenges one by one, you can build powerful and efficient CI/CD pipelines.

Happy coding!


This content originally appeared on DEV Community and was authored by Hritik Raj