A small Python Flask web app that deploys itself. When I push code to GitHub, a Jenkins pipeline on an AWS EC2 server runs the tests, builds a Docker image and replaces the running container with the new version. Nginx sits in front of the app as a reverse proxy.
I built this project to learn how a basic CI/CD pipeline works from start to end.
Flow in short:
- I push code to the
mainbranch on GitHub. - GitHub sends a webhook to Jenkins running on EC2.
- Jenkins pulls the code and runs the stages in the
Jenkinsfile: Checkout, Test, Build Image, Deploy, Health Check. - The new Docker container runs the app with Gunicorn on port 5000 (only reachable from inside the server).
- Nginx listens on port 80 and forwards public traffic to the container.
If any test fails, the pipeline stops and the old version keeps running.
| Tool | What it does in this project |
|---|---|
| Python + Flask | The web application |
| pytest | Automated tests |
| Gunicorn | Production web server inside the container |
| Git + GitHub | Version control and source of truth for the code |
| Docker | Packages the app and its dependencies into an image |
| Jenkins | Runs the CI/CD pipeline |
| AWS EC2 (Ubuntu 24.04) | The cloud server where everything runs |
| Nginx | Reverse proxy on port 80 |
| Linux / Bash | Server setup and pipeline shell steps |
devops-flask-app/
├── app.py # Flask application
├── templates/
│ └── index.html # Home page HTML
├── test_app.py # pytest tests
├── requirements.txt # Pinned Python packages
├── gunicorn.conf.py # Gunicorn settings (port, workers)
├── Dockerfile # How to build the Docker image
├── .dockerignore # Files kept out of the image
├── Jenkinsfile # CI/CD pipeline definition
├── nginx/
│ └── flask-app.conf # Nginx reverse proxy config
├── scripts/
│ └── ec2-setup.sh # Installs Docker, Jenkins, Nginx on EC2
└── docs/
├── architecture.png
├── SETUP_GUIDE.md # Step by step deployment guide
├── CHALLENGES.md # Problems I faced and how I fixed them
├── RESUME.md # Resume description of the project
└── Project_Report.pdf # Full project report in simple English
python -m venv venv
venv\Scripts\activate # on Linux or Mac: source venv/bin/activate
pip install -r requirements.txt
pytest
python app.pyOpen http://localhost:5000 and http://localhost:5000/health
docker build -t flask-app .
docker run -d -p 5000:5000 flask-app| URL | Response |
|---|---|
/ |
Home page showing the current build number |
/health |
{"status": "ok"} used by the pipeline health check |
| Stage | What happens |
|---|---|
| Checkout | Jenkins pulls the latest code from GitHub |
| Test | Creates a virtual environment and runs pytest |
| Build Docker Image | Builds flask-app:<build number> and flask-app:latest |
| Deploy | Stops the old container and starts the new one |
| Health Check | Calls /health until the app answers, fails the build if it never does |
The Jenkins build number is passed to the container as APP_VERSION, so the home page shows which build is live.
I tried to define every value in one place only, so a change never has to be made in two files:
| Value or code | Defined once in | Used by |
|---|---|---|
| App port | PORT env variable (Dockerfile) and APP_PORT (Jenkinsfile) |
Flask, Gunicorn, docker run, health check |
| Image and container names | environment block in the Jenkinsfile |
Build, Deploy and Health Check stages |
| Nginx config | nginx/flask-app.conf |
The setup script downloads it from this repo instead of keeping a second copy |
| Server settings (repo, swap size, log file) | Variables at the top of scripts/ec2-setup.sh |
The whole script |
| Home page HTML | templates/index.html |
Flask render_template |
| Test setup | A pytest fixture plus parametrize |
All tests share one client, status code checks share one test |
The project was built in 7 phases. Each phase is one commit and has a git tag (phase-1 to phase-7), so you can see how it grew:
| Phase | What was added |
|---|---|
| 1 | Flask app and tests |
| 2 | Docker (Dockerfile, Gunicorn) |
| 3 | EC2 server setup script |
| 4 | Nginx reverse proxy |
| 5 | Jenkins pipeline (Jenkinsfile) |
| 6 | GitHub webhook and setup guide |
| 7 | Documentation, architecture diagram and project report |
Full steps are in docs/SETUP_GUIDE.md.
See docs/CHALLENGES.md.
- HTTPS with a domain name and Let's Encrypt
- Push images to Docker Hub or Amazon ECR instead of building on the same server
- Run Jenkins on a separate server from the app
- Slack or email notifications when a build fails
- Infrastructure as code with Terraform
Radhika Agarwal, B.Tech Artificial Intelligence and Data Science
GitHub: radhikaa06
