DEV Community

Cover image for Building a Student REST API with Flask and Docker
Emidowojo
Emidowojo

Posted on

Building a Student REST API with Flask and Docker

Hey there! I recently built a REST API to manage student records using Flask, Flask-SQLAlchemy, and SQLite, and shared it in my last post. Now, I have taken it to the next level by containerizing it with Docker, making it portable and consistent across machines.

In this post, I will walk you through how I Dockerized my API, including the mistakes I made and how I fixed them, so you can follow along or avoid my pitfalls.


Why Docker?

Docker packages your app with its dependencies into a container, ensuring it runs the same everywhere, your laptop, a server, or the cloud. For my Student API, this meant no more “it works on my machine” excuses.

Let’s dive into the steps I took, bugs and all.


Step 1: Writing the Dockerfile

I started by creating a Dockerfile in my project folder (student-api-new/) to define how to build the Docker image. I used a multi-stage build to keep the image small: one stage for installing dependencies, another for running the app.

Stage 1: Build dependencies

# Use the slim version of Python 3.13 as base image
FROM python:3.13-slim AS builder

# Set working directory in the container
WORKDIR /app

# Update package list
RUN apt-get update

# Install build tools like gcc, then clean up
RUN apt-get install -y --no-install-recommends gcc && \
    apt-get autoremove -y && \
    rm -rf /var/lib/apt/lists/*

# Copy the requirements file into the container
COPY requirements.txt .

# Install required Python packages without caching
RUN pip install --no-cache-dir -r requirements.txt

# Install Gunicorn explicitly for running the app
RUN pip install --no-cache-dir gunicorn==23.0.0
Enter fullscreen mode Exit fullscreen mode

Stage 2: Runtime image

# Start again from a clean slim Python image
FROM python:3.13-slim

# Set working directory again for final image
WORKDIR /app

# Copy installed Python packages from the builder stage
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages

# Copy Gunicorn from builder stage
COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn

# Copy your actual Flask app code
COPY app/ ./app/

# Create non-root user for better security
RUN useradd -m appuser && chown -R appuser:appuser /app

# Switch to the new user
USER appuser

# Expose the port the app runs on
EXPOSE 5000

# Set the Flask app environment variable
ENV FLASK_APP=app

# Command to run the Flask app with Gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:create_app()"]
Enter fullscreen mode Exit fullscreen mode

What I Learned

  • Multi-Stage Builds: The builder stage handles heavy lifting (like installing gcc), but the final image only includes essentials, keeping it around 235MB.
  • Slim Base Image: python:3.13-slim is lighter than the full Python image.
  • Non-Root User: Using appuser improves security for production.

Step 2: Adding a .dockerignore

To reduce the image size, I created a .dockerignore file to exclude unnecessary files, like my virtual environment and test folder:

.venv/
.env
__pycache__/
*.pyc
students.db
.git/
.gitignore
tests/
README.md
Makefile
Enter fullscreen mode Exit fullscreen mode

This ensured only the app code and dependencies went into the container, saving space.


Step 3: Building the Docker Image

I built the image with a semantic version tag (avoiding latest, which I learned is a no-no for reproducibility):

# Build the Docker image and tag it
docker build -t student-api:1.0.0 .
Enter fullscreen mode Exit fullscreen mode

Step 4: Running the Container

To run the API, I used a command that maps port 5000 and loads environment variables from a .env file:

# Run the container in detached mode and pass in environment variables
docker run -d -p 5000:5000 --env-file .env student-api:1.0.0
Enter fullscreen mode Exit fullscreen mode

Contents of my .env file:

FLASK_ENV=development
DATABASE_URL=sqlite:///students.db
SECRET_KEY=mysecretkey
Enter fullscreen mode Exit fullscreen mode

Step 5: Testing the API

With the container running, I tested it using curl:

# Check if the API is healthy
curl http://localhost:5000/api/v1/healthcheck
Enter fullscreen mode Exit fullscreen mode

Output:

## Output: {"status":"healthy"}
Enter fullscreen mode Exit fullscreen mode
# Add a student record
curl -X POST http://localhost:5000/api/v1/students \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "age": 20}'
Enter fullscreen mode Exit fullscreen mode

Output:

{"id":1,"name":"Alice","age":20}
Enter fullscreen mode Exit fullscreen mode

Seeing those responses felt like such a huge win considering all the errors I encountered 🥲 My API was finally alive in Docker.


Step 6: Updating Documentation

I updated my README.md to include Docker instructions, so anyone could try it.

Setup (Docker)

Prerequisites

  • Docker installed (docker --version)

Build the Image

make docker-build
Enter fullscreen mode Exit fullscreen mode

Run the Container

make docker-run
Enter fullscreen mode Exit fullscreen mode

I also added Makefile targets to simplify things:

# Makefile for building and running Docker image

docker-build:
    docker build -t student-api:1.0.0 .

docker-run:
    docker run -d -p 5000:5000 --env-file .env student-api:1.0.0
Enter fullscreen mode Exit fullscreen mode

What I Learned: Good docs make your project accessible. The Makefile was a lifesaver for quick commands.


Step 7: Committing to GitHub

I wanted to share my Docker setup on GitHub, so I committed the Dockerfile and other files:

# Add Docker setup files to Git
git add Dockerfile .gitignore

# Commit with message
git commit -m "Add Docker setup for Student API"

# Push to GitHub
git push origin main
Enter fullscreen mode Exit fullscreen mode

Errors I Ran Into (and How I Fixed Them)

1. Slow Build Time (17+ Minutes)

Error:

[+] Building 1050.2s (8/12)
=> [builder 4/4] RUN apt-get update && apt-get install ... 970.8s
Enter fullscreen mode Exit fullscreen mode

Why: My internet connection was slow, which bogged down apt-get.

Fix:

# Check network speed
curl -o /dev/null http://deb.debian.org/debian/dists/bookworm/InRelease

# Clear Docker build cache
docker builder prune
Enter fullscreen mode Exit fullscreen mode

Also split apt-get update into a separate RUN to cache it better. My build time dropped to ~1–2 minutes afterwards.


2. Gunicorn Not Found

Error:

docker: Error response from daemon: ... exec: "gunicorn": executable file not found in $PATH
Enter fullscreen mode Exit fullscreen mode

Why: I forgot to add gunicorn to requirements.txt, and didn’t rebuild properly.

Fix:

# requirements.txt
Flask==3.1.0
Flask-SQLAlchemy==3.1.1
gunicorn==23.0.0
Enter fullscreen mode Exit fullscreen mode

Then in Dockerfile:

RUN pip install --no-cache-dir gunicorn==23.0.0
COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn
Enter fullscreen mode Exit fullscreen mode

Rebuild clean:

docker build --no-cache -t student-api:1.0.0 .
Enter fullscreen mode Exit fullscreen mode

3. Container Conflict When Rebuilding

Error:

Error response from daemon: conflict: unable to delete student-api:1.0.0 ... container 25ae709a77aa is using it
Enter fullscreen mode Exit fullscreen mode

Fix:

# List all containers (even stopped)
docker ps -a

# Remove the stopped container
docker rm 25ae709a77aa

# Remove the image
docker rmi student-api:1.0.0
Enter fullscreen mode Exit fullscreen mode

4. Git Ignoring Dockerfile

Error:

The following paths are ignored by one of your .gitignore files: Dockerfile
Enter fullscreen mode Exit fullscreen mode

Fix: Removed Dockerfile* from .gitignore:

.venv/
.env
__pycache__/
*.pyc
students.db
.git/
.gitignore
tests/
docker-compose*
*.dockerignore
Enter fullscreen mode Exit fullscreen mode

Then committed successfully.


5. VS Code Warning About Gunicorn

Error:

Package gunicorn is not installed in the selected environment
Enter fullscreen mode Exit fullscreen mode

Why: I hadn't installed gunicorn in my local .venv/.

Fix:

# Activate virtual environment
source .venv/bin/activate

# Install dependencies locally
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

What I Learned Overall

These errors taught me to:

  • Rebuild Images: Always rebuild after changing requirements.txt or Dockerfile.
  • Check Logs: docker logs <container-id> is your best debugging buddy.
  • Clean Up: Remove old containers/images to avoid conflicts.
  • Document Everything: Clear steps save time later.

What’s Next?

Dockerizing my API was a huge win, as it is now portable and production-ready. My next steps are:

  • Pushing student-api:1.0.0 to Docker Hub to share with others.
  • Adding endpoints like GET /students or DELETE.
  • Writing more tests to ensure reliability.

Thanks for reading! If you’re Dockerizing your own project, I hope my mistakes save you some headaches. Drop a comment with your tips or questions, I’d love to hear them ✨

Top comments (7)

Collapse
 
vepeca profile image
vepeca

Building a Student REST API with Flask and Docker is a great way to learn microservices — perfect for backend projects! It's as thrilling as a ride in Buggy Dubai across the sand dunes!
Just Dockerize your Flask app, expose endpoints, and you're good to go!

Collapse
 
vepeca profile image
vepeca

Building a Student REST API with Flask and Docker allows scalable deployment of student data services.
Projects like this can demonstrate backend skills, even as topics like "US Immigration Revokes Visas" dominate tech headlines.

Collapse
 
anuj_kumar_b93489c2c32391 profile image
Anuj Kumar

Nice

Collapse
 
anuj_kumar_b93489c2c32391 profile image
Anuj Kumar

[Here I am for the c++programing language and all languages for computer and ai related ****](_url)_

Collapse
 
jeffdev03 profile image
jeffdev03

Pretty cool stuff!

Collapse
 
alansealy0914 profile image
alansealy0914

I agree with jeffdev03... Really cool stuff! Thanks!

Collapse
 
anuj_kumar_b93489c2c32391 profile image
Anuj Kumar

I am anuj and I am from India