This project demonstrates a three-tier architecture using:
- Backend: Flask (Python web framework), SQLAlchemy (ORM)
- Database: MySQL (database)
- Frontend: Next.js (React-based SSR framework)
It showcases full-stack application development, container orchestration, and production-aligned patterns like service health checks, environment-based configuration, and database migrations.
- Build a clean separation of concerns using a three-tier model.
- Implement Flask APIs to handle core business logic and persistence.
- Use MySQL as a relational database backend.
- Serve a modern Next.js frontend.
- Deploy all services using Docker Compose.
- Add production-aware patterns like health checks, entrypoint wait scripts, and environment management.
Here's a simplified architecture diagram:
flask-student-management-app
.github/workflows/
βββ backend.yml. # implement CICD workflow for the backend codebase
βββ frontend.yml. # implement CICD workflow for the frontend codebase
backend/
βββ app/
β βββ models/ # SQLAlchemy models for Student, Course, Enrollment
β βββ routes/ # Flask Blueprints (API endpoints)
βββ migrations/ # Flask-Migrate scripts
βββ .dockerignore # dockerignore file
βββ flask.dockerfile # Dockerfile for Flask service
βββ main.py # App entrypoint
βββ requirements.txt # requirements for Flask app
βββ wait-for-mysql.sh # Wait script for MySQL readiness
frontend/
βββ (Your Next.js app here)
.gitignore
compose.yml # Docker Compose for all services
.env # Environment variables
README.md
This section guides you through setting up and running the Flask backend locally on your machine for development and testing purposes.
- Clone the Repository
git clone https://github.com/intellisenseCodez/flask-student-management-app.git
cd flask-student-management-app
- Configure Flask Environment Variables
Inside the
backend/
directory, create a.flaskenv
file:
cd backend
touch .flaskenv
Add the following content to .flaskenv
:
# Flask Environment Configuration
FLASK_APP=main.py
FLASK_ENV=development
FLASK_RUN_PORT=8080
FLASK_RUN_HOST=127.0.0.1
DEBUG=1
# SQLAlchemy connection URI
DATABASE_URL=mysql+pymysql://root:your-password@localhost:3306/student_db
- Replace
your-password
with your actual MySQL root password. .flaskenv
is automatically used by Flask when running locally if you have the python-dotenv package installed.
Note:
Ensure that MySQL Server is installed and running on your local system. You can download it here: MySQL Download.
- Create a MySQL Database You can create the database using MySQL Workbench or the MySQL CLI:
CREATE DATABASE IF NOT EXISTS student_db;
- Set Up a Python Virtual Environment
From inside the
backend/
directory:
# Create a virtual environment
python3 -m venv venv
# Activate the virtual environment
source venv/bin/activate # For Linux/macOS
# OR
venv\Scripts\activate # For Windows
Reference
: Learn more about Python virtual environments page.
- Install Python Dependencies After activating the virtual environment, install the required packages:
pip install -r requirements.txt
- Run Database Migrations
Use
Flask-Migrate
to create the tables in your student_db:
flask db upgrade
Reference
: Learn more about Flask Migration page.
- Start the Flask Development Server Finally, run the application:
flask run
By default, it will be available at: π http://127.0.0.1:8080
In backend/
, we have a file named flask.dockerfile:
# Python base image
FROM python:3.11-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
gcc \
curl \
&& rm -rf /var/lib/apt/lists/*
# Install mysqladmin
RUN apt-get update && \
apt-get install -y default-mysql-client && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
# Copy project files
COPY . .
# Copy the MySQL wait script into the container
COPY wait-for-mysql.sh .
# Make the wait script executable
RUN chmod +x wait-for-mysql.sh
# Set environment variables for Flask
ENV FLASK_APP=main.py
ENV FLASK_RUN_PORT=8080
ENV FLASK_RUN_HOST=0.0.0.0
# Expose the application port
EXPOSE 8080
# Python base image
FROM python:3.11-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
gcc \
curl \
&& rm -rf /var/lib/apt/lists/*
# Install mysqladmin
RUN apt-get update && \
apt-get install -y default-mysql-client && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
# Copy project files
COPY . .
# Copy the MySQL wait script into the container
COPY wait-for-mysql.sh .
# Make the wait script executable
RUN chmod +x wait-for-mysql.sh
# Set environment variables for Flask
ENV FLASK_APP=main.py
ENV FLASK_RUN_PORT=8080
ENV FLASK_RUN_HOST=0.0.0.0
# Expose the application port
EXPOSE 8080
# Run wait-for-mysql, apply migrations, then start Flask server
CMD ["sh", "-c", "./wait-for-mysql.sh && flask db upgrade && flask run --host=0.0.0.0 --port=8080"]
we also have backend/wait-for-mysql.sh
:
The wait-for-mysql.sh
script ensures that your Flask backend only starts after the MySQL database is fully up and ready to accept connections.
#!/bin/bash
echo "Waiting for MySQL to become healthy..."
until mysqladmin ping -h"$MYSQL_HOST" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" --silent; do
sleep 2
done
echo "MySQL is up!"
exec "$@"
Create a .env file in the project root:
# MySQL Environment Variables
MYSQL_HOST="database"
MYSQL_ROOT_PASSWORD="rootpassword"
MYSQL_DATABASE="studentdb"
MYSQL_USER="testuser"
MYSQL_PASSWORD="testpassword"
# SQLAlchemy connection URI
DATABASE_URL=mysql+pymysql://testuser:testpassword@database:3306/student_db
# dockerhub username
DOCKERHUB_USERNAME=your-username
In your root directory we have compose.yml:
services:
# backend service
backend:
container_name: flask-student-api
restart: always
image: ${DOCKERHUB_USERNAME}/flask-student-api:v1.0
build:
context: ./backend
dockerfile: flask.dockerfile
ports:
- 8080:8080
env_file: ".env"
environment:
- DATABASE_URL=mysql+pymysql://${MYSQL_USER}:${MYSQL_PASSWORD}@database:3306/${MYSQL_DATABASE}
depends_on:
- database
# database service
database:
container_name: mysql-database
image: mysql:5.7
env_file: ".env"
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
ports:
- 3307:3306
volumes:
- mysql_data:/var/lib/mysql
# frontend service
volumes:
mysql_data: {}
Ensure you are in the root project:
# run in non-detached mode
docker-compose up --build
OR
# run in detached mode
docker-compose up -d --build
Once the services are up, your Flask API should be available at: π http://localhost:8080
β Testing the API π§ͺ Testing the API Using Postman Once your Flask backend is up and running (either locally or via Docker), you can use Postman β a popular API testing tool β to interact with your API endpoints.
Method | Endpoint | Description |
---|---|---|
POST | /api/v1.0/student/create |
Create a new student |
GET | /api/v1.0/students/all |
Retrieve all students |
GET | /api/v1.0/students/<student-id> |
Retrieve student by ID |
GET | /api/v1.0/students/by-course?course_title="your course" |
Retrieve students by course title |
PUT | /api/v1.0/students/<student-id> |
Update student by ID |
DELETE | /api/v1.0/students/<student-id> |
Delete student by ID |
POST | /api/v1.0/course/create |
Create a new course |
GET | /api/v1.0/course/all |
Retrieve all courses |
GET | /api/v1.0/course/<course-id> |
Retrieve course by ID |
PUT | /api/v1.0/courses/<course-id> |
Update course by ID |
DELETE | /api/v1.0/courses/<course-id> |
Delete course by ID |
POST | /api/v1.0/course/add/<course-id> |
Enroll student for a course |
GET | /api/v1.0/students/<student-id>/courses |
Retrieve all enrolled courses by student |
This project includes a CI/CD pipeline that automatically builds, pushes, and deploys the Flask API as a Docker image whenever changes are made to the backend codebase. It is implemented using GitHub Actions and a self-hosted AWS runner.
The CI/CD workflow is defined in .github/workflows/backend.yml
.yml. It contains the following jobs:
-
Trigger:
The workflow runs when:
-
A push is made to the master branch with changes in backend/**.
-
The workflow file itself
(.github/workflows/backend.yml)
is updated. -
It can also be triggered manually using workflow_dispatch.
-
-
Steps:
- Checkout Code:
Uses
actions/checkout@v4
to pull the latest code from GitHub.-
Build Flask Docker Image:
Builds the backend image using the flask.dockerfile:
docker build --file backend/flask.dockerfile -t ${{ secrets.DOCKERHUB_USERNAME }}/flask-student-api:v1.0 backend
-
Login to Docker Hub:
Authenticates with Docker Hub using docker/login-action@v3 and secrets:
- DOCKERHUB_USERNAME
- DOCKERHUB_TOKEN
-
Push Image to Docker Hub:
Pushes the image to the Docker Hub registry:
docker push ${{ secrets.DOCKERHUB_USERNAME }}/flask-student-api:v1.0
Two notification jobs are configured to inform the team of pipeline status:
-
notify-success: Sends an email if the build-and-deploy job succeeds.
-
notify-fail: Sends an email if the build-and-deploy job fails.
Emails are sent using the dawidd6/action-send-mail@v6 action and the following secrets:
-
MAIL_USERNAME
-
MAIL_PASSWORD
-
RECEIVER_EMAIL_ADDERESS
-
SENDER_EMAIL_ADDERESS
Once the image is successfully pushed to Docker Hub, the pipeline:
- Generates a
.env
file on the self-hosted AWS runner, using variables and secrets from GitHub:
cat <<EOF > .env
DOCKERHUB_USERNAME=${{ vars.DOCKERHUB_USERNAME }}
MYSQL_USER=${{ vars.MYSQL_USER }}
MYSQL_PASSWORD=${{ secrets.MYSQL_PASSWORD }}
MYSQL_DATABASE=${{ vars.MYSQL_DATABASE }}
MYSQL_ROOT_PASSWORD=${{ secrets.MYSQL_ROOT_PASSWORD }}
DATABASE_URL=mysql+pymysql://${{ vars.MYSQL_USER }}:${{ secrets.MYSQL_PASSWORD }}@database:3306/${{ vars.MYSQL_DATABASE }}
EOF
- Runs Docker Compose to start the Flask API and MySQL containers:
docker compose up -d
The workflow depends on the following secrets and repository variables configured in the repository settings:
-
DOCKERHUB_USERNAME β Docker Hub username.
-
DOCKERHUB_TOKEN β Docker Hub access token or password.
-
MYSQL_PASSWORD β Database user password.
-
MYSQL_ROOT_PASSWORD β MySQL root password.
-
MAIL_USERNAME β Email SMTP username.
-
MAIL_PASSWORD β Email SMTP password.
-
RECEIVER_EMAIL_ADDERESS β Email address to receive notifications.
-
SENDER_EMAIL_ADDERESS β Email address used as the sender.
-
DOCKERHUB_USERNAME β Docker Hub username (can also be a secret).
-
MYSQL_USER β MySQL user.
-
MYSQL_DATABASE β MySQL database name.
-
Go to AWS EC2 Console.
-
Launch an instance (e.g., Ubuntu 22.04 LTS or Amazon Linux 2).
-
Choose an instance type (e.g., t2.micro or larger).
-
Configure security groups:
- Allow SSH (22).
- Allow HTTP (80), HTTPS (443), and Custom TCP (8080) for your Flask API.
-
Download the
.pem
key for SSH access.
- Open an SSH client.
- Locate your private
.pem
key file. - Run this command, if necessary, to ensure your key is not publicly viewable.
chmod 400 "github-action-runner-login.pem"
- Connect to your instance using its Public DNS:
ssh -i "your-private-pem-file.pem" ubuntu@your-public-DNS
(Log out and log back in for changes to take effect.)
-
Go to your GitHub repository β Settings β Actions β Runners β New self-hosted runner.
-
Select Linux as the platform.
-
Follow the instructions to download and configure the runner.
-
Ensure Docker and Docker Compose are installed and running.
-
Your CI/CD workflow will generate a .env file on the self-hosted runner during deployment.
-
The runner will execute docker compose up -d to start the Flask API and database.
Testing the Deployment
This project was built for learning and demonstration purposes. Inspired by real-world backend system design and frontend integration practices.