A hands-on DevOps portfolio project demonstrating the deployment, security hardening, monitoring, and automated testing of a containerized Flask web application on Ubuntu Server.
The project uses Docker, Docker Compose, Nginx, UFW, MariaDB, Git, and GitHub Actions to create a small but practical Linux infrastructure environment.
Client
|
v
UFW Firewall
|
v
Nginx :80
|
v
127.0.0.1:5000
|
v
Docker Container
|
v
Flask Application
MariaDB
127.0.0.1:3306
Nginx acts as the public-facing reverse proxy while the Flask application and MariaDB are restricted to localhost.
- Ubuntu Server 24.04 LTS
- Docker
- Docker Compose
- Python / Flask
- Nginx
- UFW Firewall
- MariaDB
- Git
- GitHub
- GitHub Actions
The Flask application runs inside a Docker container and provides:
- Web application endpoint
/healthhealth-check endpoint- Docker container health monitoring
- Docker Compose service management
Example health response:
{"status":"healthy"}Nginx listens on port 80 and forwards requests to the Flask application running on:
127.0.0.1:5000
The Flask container is not published on all network interfaces.
UFW is enabled with only the required inbound services allowed:
22/tcp - SSH
80/tcp - HTTP
Application port 5000 and database port 3306 are not opened through UFW.
MariaDB is bound to:
127.0.0.1:3306
This prevents the database service from being directly exposed to the network.
A dedicated application database and database user were created instead of using the MariaDB root account.
The application database user is restricted to:
devops_user@localhost
and its privileges are scoped to the application database.
Database passwords and other credentials are intentionally not stored in this repository.
Docker Compose continuously checks the Flask health endpoint:
http://localhost:5000/health
Container status can be verified with:
docker compose psGitHub Actions automatically validates the project whenever changes are pushed to the main branch.
The CI workflow:
- Checks out the repository
- Builds the Docker image
- Starts the container
- Waits for the application
- Tests the
/healthendpoint - Confirms the container is running
This provides automated validation that new changes do not break the Dockerized application.
The project follows several basic infrastructure security principles:
- Minimize publicly exposed services
- Use Nginx as the application entry point
- Restrict Flask to localhost
- Restrict MariaDB to localhost
- Enable host firewall rules
- Keep SSH access explicitly allowed
- Avoid using database root credentials for applications
- Validate configurations before reloading services
- Verify application health after infrastructure changes
- Keep application configuration under version control
UFW limits inbound access to the required services, while the Flask application and MariaDB are bound to localhost.
The Docker Compose service includes an automated health check to verify application availability.
docker compose pscurl http://127.0.0.1:5000/healthcurl http://localhost/healthsudo nginx -tsudo ufw status verbosesudo ss -tulpnsudo ss -tulpn | grep 3306Infrastructure changes are applied incrementally and verified immediately afterward.
Before reloading Nginx:
sudo nginx -tAfter application or infrastructure changes:
docker compose ps
curl http://localhost/healthGit provides configuration history so previous known-good application configurations can be identified and restored if necessary.
git log --onelineBuild and start the application:
docker compose up -d --buildCheck its status:
docker compose psTest through Nginx:
curl http://localhost/healthStop the application:
docker compose downThe GitHub Actions workflow is located at:
.github/workflows/ci.yml
Each push to main automatically triggers the Docker build and application health test.
The CI pipeline automatically builds and tests the Dockerized application after changes are pushed to the main branch.
devops-project/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .dockerignore
├── .gitignore
├── Dockerfile
├── docker-compose.yml
├── app.py
├── requirements.txt
└── README.md
A recorded demonstration of the infrastructure covers:
- Dockerized Flask deployment
- Nginx reverse proxy configuration
- Listening-port verification
- UFW firewall hardening
- MariaDB network and account restrictions
- Application health verification
- Rollback approach
Demo video: Watch the DevOps Practical Demo
This project strengthened my practical understanding of Linux server administration and DevOps workflows, particularly around service exposure, reverse proxies, firewall configuration, database access control, Docker networking, health monitoring, Git-based change management, and automated CI testing.
It also reinforced an important operational principle: infrastructure changes should be understood before execution, applied incrementally, verified immediately, and accompanied by a clear rollback path.
Joseph Nicolas
DevOps / IT Infrastructure / Automation
Portfolio: https://itguy2024.github.io/portfolio/


