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
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()"]
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
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 .
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
Contents of my .env
file:
FLASK_ENV=development
DATABASE_URL=sqlite:///students.db
SECRET_KEY=mysecretkey
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
Output:
## Output: {"status":"healthy"}
# Add a student record
curl -X POST http://localhost:5000/api/v1/students \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "age": 20}'
Output:
{"id":1,"name":"Alice","age":20}
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
Run the Container
make docker-run
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
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
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
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
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
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
Then in Dockerfile:
RUN pip install --no-cache-dir gunicorn==23.0.0
COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn
Rebuild clean:
docker build --no-cache -t student-api:1.0.0 .
3. Container Conflict When Rebuilding
Error:
Error response from daemon: conflict: unable to delete student-api:1.0.0 ... container 25ae709a77aa is using it
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
4. Git Ignoring Dockerfile
Error:
The following paths are ignored by one of your .gitignore files: Dockerfile
Fix: Removed Dockerfile*
from .gitignore
:
.venv/
.env
__pycache__/
*.pyc
students.db
.git/
.gitignore
tests/
docker-compose*
*.dockerignore
Then committed successfully.
5. VS Code Warning About Gunicorn
Error:
Package gunicorn is not installed in the selected environment
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
What I Learned Overall
These errors taught me to:
-
Rebuild Images: Always rebuild after changing
requirements.txt
orDockerfile
. -
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
orDELETE
. - 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)
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!
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.
Nice
[Here I am for the c++programing language and all languages for computer and ai related ****](_url)_
Pretty cool stuff!
I agree with jeffdev03... Really cool stuff! Thanks!
I am anuj and I am from India