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
Project Structure
Here is the structure used:
project/
├── app.py
├── requirements.txt
├── tests/
│ └── test_app.py
├── Dockerfile
├── .gitlab-ci.yml
└── sonar-project.properties
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)
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
These tests ensure:
- the API responds correctly
- endpoints are reachable
Same as defined here
Dependencies
requirements.txt
flask
pytest
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"]
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
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"]
This is exactly how the pipeline is structured in the project
Pipeline Explanation
1. Test Stage
Runs:
pytest
Purpose:
- validate code before build
- stop pipeline if tests fail
2. Build Stage
Runs:
docker build
Purpose:
- create a deployable image
- ensure reproducibility
3. SonarQube Stage
Runs:
sonar-scanner
Purpose:
- analyze code quality
- detect issues early
Note: this stage is non-blocking.
4. Deploy Stage
Runs:
docker run
Purpose:
- stop old container
- start new version
Running the Project Locally
Without Docker
pip install -r requirements.txt
pytest
python app.py
Test:
curl http://localhost:5000
With Docker
docker build -t python-app .
docker run -d -p 5000:5000 python-app
Troubleshooting
Tests fail
- fix code before deploying
- expected behavior
Docker build fails
- check
requirements.txt - check
CMDand file names
Container not running
docker ps -a
docker logs python-app-dev
SonarQube issues
- ensure scanner is available
- use
allow_failure: trueif 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)