hey-cicd/
├── app/
│ ├── app.py # Flask application
│ ├── templates/
│ │ └── index.html # Dashboard UI
│ └── static/
│ ├── css/styles.css
│ └── js/main.js
├── tests/
│ └── test_app.py # Unit tests
├── k8s/
│ ├── deployment.yaml # Kubernetes Deployment
│ └── service.yaml # Kubernetes Service
├── .github/
│ └── workflows/
│ └── devsecops.yml # CI/CD Pipeline
├── Dockerfile
├── requirements.txt
├── requirements-dev.txt
└── README.md
| Method | Route | Description |
|---|---|---|
GET |
/ |
Dashboard UI |
GET |
/health |
Health check |
GET |
/api/status |
App info, uptime, Python version |
GET |
/api/greet/<name> |
Returns a greeting for the name |
POST |
/api/add |
Adds two numbers |
POST |
/api/calculate |
Calculator (add/subtract/multiply/divide/power/modulo) |
POST |
/api/pipeline/run |
Simulates a CI/CD pipeline run |
git clone https://github.com/YOUR_USERNAME/hey-cicd.git
cd hey-cicdpython3 -m venv .venv
source .venv/bin/activate # Mac/Linux
# .venv\Scripts\activate # Windowspip install -r requirements.txtpython3 app/app.pyhttp://localhost:5001
pip install -r requirements-dev.txt
python3 -m pytest --cov=app --cov-report=term-missingExpected output:
tests/test_app.py::test_home PASSED
tests/test_app.py::test_health PASSED
tests/test_app.py::test_greet PASSED
tests/test_app.py::test_add_numbers PASSED
tests/test_app.py::test_add_numbers_missing_fields PASSED
tests/test_app.py::test_calculator_multiply PASSED
tests/test_app.py::test_calculator_divide_by_zero PASSED
tests/test_app.py::test_status PASSED
8 passed in 0.Xs
# Health check
curl http://localhost:5001/health
# Greet someone
curl http://localhost:5001/api/greet/Nensi
# Add two numbers
curl -X POST http://localhost:5001/api/add \
-H "Content-Type: application/json" \
-d '{"number1": 10, "number2": 20}'
# Calculator
curl -X POST http://localhost:5001/api/calculate \
-H "Content-Type: application/json" \
-d '{"a": 6, "b": 3, "operation": "multiply"}'- Docker Desktop installed and running
docker build -t hey-cicd:latest .docker run -p 5001:5001 hey-cicd:latesthttp://localhost:5001
# See running containers
docker ps
# Stop the container
docker stop <container-id>
# Remove the image
docker rmi hey-cicd:latest
# Run in background (detached mode)
docker run -d -p 5001:5001 hey-cicd:latestThe pipeline runs automatically every time you push code to main or open a pull request.
Push to GitHub
│
▼
┌─────────────────┐
│ STEP 1: Tests │ pytest — runs all 8 unit tests
└────────┬────────┘
│
┌────────▼────────┐
│ STEP 2: SAST │ CodeQL — scans code for security issues
└────────┬────────┘
│
┌────────▼────────┐
│ STEP 3: SCA │ pip-audit — checks for vulnerable packages
└────────┬────────┘
│ (all 3 must pass)
┌────────▼────────┐
│ STEP 4: Build │ docker build — creates the Docker image
└────────┬────────┘
│
┌────────▼────────┐
│ STEP 5: Scan │ Trivy — scans the Docker image for CVEs
└────────┬────────┘
│
┌────────▼────────┐
│ STEP 6: Push │ Pushes image to GitHub Container Registry
└────────┬────────┘
│ (only on push to main)
┌────────▼────────┐
│ STEP 7: Deploy │ kubectl apply → deploys to Kubernetes
└─────────────────┘
# Make a change, commit, and push
git add .
git commit -m "your message"
git push origin mainThen go to your GitHub repo → Actions tab to watch it run.
Go to GitHub repo → Settings → Secrets and variables → Actions and add:
| Secret Name | Value |
|---|---|
KUBECONFIG |
Contents of your ~/.kube/config file (needed for Step 7 deploy) |
ℹ️
GITHUB_TOKENis automatically provided by GitHub — you don't need to add it manually.
After Step 6 runs, your image is available at:
ghcr.io/YOUR_USERNAME/hey-cicd:latest
Go to GitHub repo → Packages to see it.
Do this if you want to deploy without the pipeline, directly from your terminal.
- A running Kubernetes cluster (minikube, k3s, or cloud)
kubectlinstalled and connected to your cluster
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yamlkubectl get pods
kubectl get service session17-python# If using minikube
minikube service session17-python
# Or access via NodePort
http://<your-node-ip>:30001# See all running pods
kubectl get pods
# See logs from a pod
kubectl logs <pod-name>
# Delete the deployment
kubectl delete -f k8s/deployment.yaml
kubectl delete -f k8s/service.yaml| Concept | Tool Used | Where |
|---|---|---|
| Unit Testing | pytest + pytest-cov | tests/test_app.py |
| SAST (Static Application Security Testing) | GitHub CodeQL | Pipeline Step 2 |
| SCA (Software Composition Analysis) | pip-audit | Pipeline Step 3 |
| Containerisation | Docker | Dockerfile |
| Container Image Scanning | Trivy | Pipeline Step 5 |
| Container Registry | GitHub Container Registry (GHCR) | Pipeline Step 6 |
| Orchestration | Kubernetes | k8s/ folder |
| CI/CD Automation | GitHub Actions | .github/workflows/devsecops.yml |
- Python 3.12 + Flask 3.x
- Docker
- Kubernetes
- GitHub Actions