DEV Community

lajibolala
lajibolala

Posted on

Building a Complete CI/CD Pipeline for a Python App

In this guide, I’ll walk through a simple but complete CI/CD setup for a Python application using:

  • GitLab CI/CD
  • Docker
  • SonarQube
  • Pytest

The goal is to build a pipeline that:

Test → Build → Analyze → Deploy
Enter fullscreen mode Exit fullscreen mode

Project Structure

Here is the structure used:

project/
├── app.py
├── requirements.txt
├── tests/
│   └── test_app.py
├── Dockerfile
├── .gitlab-ci.yml
└── sonar-project.properties
Enter fullscreen mode Exit fullscreen mode

Python Application

The application is a simple Flask API with multiple endpoints.

app.py

from flask import Flask, jsonify
import os

app = Flask(__name__)

@app.route("/")
def home():
    return jsonify({"message": "UrbanHub API", "status": "ok"})

@app.route("/health")
def health():
    return jsonify({"service": "up"}), 200

@app.route("/config")
def config():
    env = os.getenv("APP_ENV", "dev")
    return jsonify({"environment": env})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
Enter fullscreen mode Exit fullscreen mode

This exposes:

  • / → main endpoint
  • /health → health check
  • /config → environment info

This matches the implementation in the project


Automated Tests

We use Pytest to validate the API.

tests/test_app.py

from app import app

def test_home():
    client = app.test_client()
    response = client.get("/")
    assert response.status_code == 200

def test_health():
    client = app.test_client()
    response = client.get("/health")
    assert response.status_code == 200
Enter fullscreen mode Exit fullscreen mode

These tests ensure:

  • the API responds correctly
  • endpoints are reachable

Same as defined here


Dependencies

requirements.txt

flask
pytest
Enter fullscreen mode Exit fullscreen mode

This is minimal and enough to:

  • run the app
  • execute tests

Dockerizing the Application

We containerize the app using Docker.

Dockerfile

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

ENV APP_ENV=production

EXPOSE 5000

CMD ["python", "app.py"]
Enter fullscreen mode Exit fullscreen mode

This ensures:

  • consistent runtime
  • easy deployment
  • reproducibility

SonarQube Configuration

sonar-project.properties

sonar.projectKey=python-app
sonar.projectName=python-app
sonar.sources=.
sonar.tests=tests
sonar.qualitygate.wait=true
Enter fullscreen mode Exit fullscreen mode

This config enables:

  • static analysis
  • quality checks
  • maintainability tracking

GitLab CI/CD Pipeline

Here is the full pipeline:

.gitlab-ci.yml

stages:
  - test
  - build
  - sonarqube
  - deploy

variables:
  IMAGE_NAME: python-app
  CONTAINER_NAME: python-app-dev

run_tests:
  stage: test
  script:
    - pip install -r requirements.txt
    - pytest

build_image:
  stage: build
  script:
    - docker build --network=host -t $IMAGE_NAME:latest .

sonarqube_check:
  stage: sonarqube
  image:
    name: sonarsource/sonar-scanner-cli:latest
    entrypoint: [""]
  script:
    - sonar-scanner
  allow_failure: true

deploy_dev:
  stage: deploy
  script:
    - docker stop $CONTAINER_NAME || true
    - docker rm $CONTAINER_NAME || true
    - docker run -d --network host --name $CONTAINER_NAME $IMAGE_NAME:latest
  needs: ["run_tests", "build_image"]
Enter fullscreen mode Exit fullscreen mode

This is exactly how the pipeline is structured in the project


Pipeline Explanation

1. Test Stage

Runs:

pytest
Enter fullscreen mode Exit fullscreen mode

Purpose:

  • validate code before build
  • stop pipeline if tests fail

2. Build Stage

Runs:

docker build
Enter fullscreen mode Exit fullscreen mode

Purpose:

  • create a deployable image
  • ensure reproducibility

3. SonarQube Stage

Runs:

sonar-scanner
Enter fullscreen mode Exit fullscreen mode

Purpose:

  • analyze code quality
  • detect issues early

Note: this stage is non-blocking.


4. Deploy Stage

Runs:

docker run
Enter fullscreen mode Exit fullscreen mode

Purpose:

  • stop old container
  • start new version

Running the Project Locally

Without Docker

pip install -r requirements.txt
pytest
python app.py
Enter fullscreen mode Exit fullscreen mode

Test:

curl http://localhost:5000
Enter fullscreen mode Exit fullscreen mode

With Docker

docker build -t python-app .
docker run -d -p 5000:5000 python-app
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Tests fail

  • fix code before deploying
  • expected behavior

Docker build fails

  • check requirements.txt
  • check CMD and file names

Container not running

docker ps -a
docker logs python-app-dev
Enter fullscreen mode Exit fullscreen mode

SonarQube issues

  • ensure scanner is available
  • use allow_failure: true if needed

Key Takeaways

  • CI/CD is about automation and reliability
  • tests should run before deployment
  • Docker ensures consistency
  • SonarQube improves code quality
  • simplicity is often better than complexity

Final Thoughts

A working CI/CD pipeline does not need to be complex.

Even a simple pipeline like this provides:

  • automated testing
  • automated deployment
  • improved code quality

The most important thing is not complexity.

It is control, consistency, and confidence in your delivery process.

Top comments (0)