DEV Community

Python-T Point
Python-T Point

Posted on • Originally published at pythontpoint.in

🚀 Deploy a Decision Tree Classifier with FastAPI

🐍 Virtual Environment — Isolation

deploy decision tree classifier fastapi

A virtual environment is a directory that provides an independent Python interpreter and package store — that is the whole concept.

📑 Table of Contents

  • 🐍 Virtual Environment — Isolation
  • 📦 Model Preparation — Training
  • 📊 Training script
  • 📁 Model file
  • 🚀 FastAPI Service — Exposure
  • 🛠 Endpoint definition
  • 🧪 Request/Response schema
  • 🐳 Containerization — Docker
  • 🌐 Production Deployment — Kubernetes
  • ⚖️ Runtime Choices — Comparison
  • 🟩 Final Thoughts
  • ❓ Frequently Asked Questions
  • Can I use a different ML library instead of scikit‑learn?
  • How do I secure the API without adding an authentication layer?
  • What is the recommended way to monitor latency in production?
  • 📚 References & Further Reading

📦 Model Preparation — Training

Training a DecisionTreeClassifier and persisting the artifact is the first step toward deploying the classifier with FastAPI.

📊 Training script

# train_model.py
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
import joblib # Load a CSV that contains feature columns and a target column named "label"
df = pd.read_csv("iris.csv")
X = df.drop(columns="label")
y = df["label"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42
) clf = DecisionTreeClassifier(max_depth=3, random_state=42)
clf.fit(X_train, y_train) # Persist the trained model
joblib.dump(clf, "model.joblib")
print("Model saved to model.joblib")
Enter fullscreen mode Exit fullscreen mode

Running the script yields:

$ python train_model.py
Model saved to model.joblib
Enter fullscreen mode Exit fullscreen mode

📁 Model file

The resulting model.joblib is a binary representation of the tree structure: each node stores a split feature index, a threshold, and pointers to child nodes. Scikit‑learn stores this table in a NumPy array, which enables O(log n) prediction by traversing from the root to a leaf. Loading the file is a single joblib.load call with near‑constant time overhead because the array is memory‑mapped.

Key point: the serialized file contains only the minimal tree data, so loading it at runtime adds negligible overhead.


🚀 FastAPI Service — Exposure

This section wraps the persisted model in a FastAPI endpoint so predictions can be served over HTTP.

🛠 Endpoint definition

# app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import joblib
import numpy as np app = FastAPI(title="Decision Tree API") # Load the model once at startup
model = joblib.load("model.joblib") class Features(BaseModel): sepal_length: float sepal_width: float petal_length: float petal_width: float @app.post("/predict")
def predict(features: Features): # Convert Pydantic model to a 2‑D NumPy array expected by scikit‑learn X = np.array([[features.sepal_length, features.sepal_width, features.petal_length, features.petal_width]]) try: pred = model.predict(X)[0] return {"prediction": pred} except Exception as e: raise HTTPException(status_code=500, detail=str(e))
Enter fullscreen mode Exit fullscreen mode

FastAPI automatically generates OpenAPI documentation at /docs. The framework runs on uvicorn , an ASGI server built on asyncio. uvicorn’s single‑process event loop eliminates the fork‑based overhead of traditional WSGI servers, while the Dockerfile’s --workers 2 flag starts a second OS process to bypass the GIL for concurrent requests.

🧪 Request/Response schema

Example request (JSON):

{ "sepal_length": 5.1, "sepal_width": 3.5, "petal_length": 1.4, "petal_width": 0.2
}
Enter fullscreen mode Exit fullscreen mode

Corresponding response (JSON):

{ "prediction": "setosa"
}
Enter fullscreen mode Exit fullscreen mode

Why this, not the obvious alternative: FastAPI provides built‑in data validation and interactive docs; a plain Flask route would require manual request parsing and error handling, increasing boilerplate.

Key point: the endpoint performs a single NumPy array conversion and a model.predict call, keeping latency under a few milliseconds for modest tree depths.


🐳 Containerization — Docker

Packaging the FastAPI service into a Docker image creates a portable artifact for deploy decision tree classifier fastapi.

# Dockerfile
FROM python:3.12-slim # Install system dependencies for scikit-learn (build tools not needed at runtime)
RUN apt-get update && apt-get install -y -no-install-recommends \ gcc libgomp1 && rm -rf /var/lib/apt/lists/* # Create a non‑root user
RUN useradd -m appuser
WORKDIR /app
COPY requirements.txt .
RUN pip install -no-cache-dir -r requirements.txt # Copy source code and model
COPY app.py .
COPY model.joblib . # Switch to non‑root user
USER appuser # Expose the default FastAPI port
EXPOSE 8000 # Run with uvicorn in production mode
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
Enter fullscreen mode Exit fullscreen mode

What this does: (Also read: ⚙️ FastAPI on GCP Cloud Run vs Compute Engine — Pricing and Performance Compared) (More onPythonTPoint tutorials)

  • FROM python:3.12-slim : base image with minimal OS footprint.
  • apt-get install gcc libgomp1 : provides the OpenMP runtime required by NumPy‑based scikit‑learn operations.
  • useradd -m appuser and USER appuser : run the service without root privileges, reducing attack surface.
  • CMD [ "uvicorn", … "-workers", "2"]: launches two worker processes; each worker holds its own model instance, enabling parallel request handling without the GIL bottleneck.

Build and run the image:

$ docker build -t dtc-api:latest .
Sending build context to Docker daemon 12.29kB
Step 1/12: FROM python:3.12-slim
...
Successfully built 1a2b3c4d5e6f
Successfully tagged dtc-api:latest
$ docker run -d -p 8000:8000 dtc-api:latest
d9f1c2e3b4a5c6d7e8f9a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9
Enter fullscreen mode Exit fullscreen mode

Key point: the container isolates the runtime, ensuring that the same binary works across development, staging, and production environments. (Also read: ☁️ Deploy MinIO on Kubernetes with Helm made easy)


🌐 Production Deployment — Kubernetes

Deploying the container to a Kubernetes cluster provides scalability and health‑checking for the workflow.

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata: name: dtc-deployment
spec: replicas: 3 selector: matchLabels: app: dtc template: metadata: labels: app: dtc spec: containers: - name: dtc-container image: dtc-api:latest ports: - containerPort: 8000 readinessProbe: httpGet: path: /docs port: 8000 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 15 periodSeconds: 30 --
apiVersion: v1
kind: Service
metadata: name: dtc-service
spec: selector: app: dtc ports: - protocol: TCP port: 80 targetPort: 8000 type: ClusterIP
Enter fullscreen mode Exit fullscreen mode

What this does:

  • replicas: 3 : runs three identical pods, giving redundancy and load‑balancing.
  • readinessProbe : signals Kubernetes when the FastAPI docs endpoint is reachable, preventing traffic to a pod that hasn't finished startup.
  • livenessProbe : periodically hits /health (which FastAPI provides automatically) to restart a pod that becomes unresponsive.
  • Service type ClusterIP : exposes the pods on an internal IP; an Ingress controller can route external traffic without assigning a public IP to each pod.

Apply the manifest:

$ kubectl apply -f deployment.yaml
deployment.apps/dtc-deployment created
service/dtc-service created
Enter fullscreen mode Exit fullscreen mode

Verify the pods are running:

$ kubectl get pods -l app=dtc
NAME READY STATUS RESTARTS AGE
dtc-deployment-5f8c9d7b9c-8l9kz 1/1 Running 0 2m30s
dtc-deployment-5f8c9d7b9c-b2h2v 1/1 Running 0 2m30s
dtc-deployment-5f8c9d7b9c-hxk7p 1/1 Running 0 2m30s
Enter fullscreen mode Exit fullscreen mode

According to the Kubernetes documentation, a Deployment controller continuously monitors the desired replica count and replaces failed pods, which is why it is preferred over a bare Pod for production workloads.

Key point: the combination of readiness/liveness probes and multiple replicas makes the service resilient to transient failures while keeping the API surface consistent.


⚖️ Runtime Choices — Comparison

This section compares the two most common ASGI servers for FastAPI: uvicorn (single‑process) and gunicorn with the uvicorn worker class (multi‑process).

Feature uvicorn gunicorn + uvicorn
Process model single process, multiple async workers multiple OS processes, each with its own async workers
Memory usage lower (shared event loop) higher (separate Python interpreters)
CPU scaling limited by GIL; best for I/O‑bound workloads full CPU utilization for CPU‑bound tasks
Deployment simplicity direct command line requires gunicorn config but integrates with existing process managers

For a DecisionTreeClassifier whose prediction is CPU‑light, uvicorn with two workers (as set in the Dockerfile) offers sufficient concurrency while keeping the container image small.


🟩 Final Thoughts

Deploying a Decision Tree Classifier with FastAPI follows a clear data path: train → serialize → load in an endpoint → containerize → orchestrate. Each layer adds a deterministic piece of infrastructure, so you can replace or extend any part without altering the core prediction logic. By keeping the model file immutable and the service stateless, horizontal scaling becomes a matter of adjusting the replica count in the Kubernetes manifest.

Because the entire stack relies on open‑source, well‑documented components, the approach works across cloud providers and on‑premise clusters alike. The resulting API is ready for integration with downstream services, A/B testing pipelines, or monitoring dashboards, making the transition from experiment to production seamless.


❓ Frequently Asked Questions

Can I use a different ML library instead of scikit‑learn?

Yes. The FastAPI endpoint only requires an object that implements a predict method accepting a NumPy‑style array. Libraries such as XGBoost or LightGBM expose compatible interfaces, so you can replace the model loading line with the appropriate serializer.

How do I secure the API without adding an authentication layer?

FastAPI supports OAuth2 and API key schemes out of the box. Adding a dependency that validates a token before the /predict handler prevents unauthorized access while keeping the codebase minimal.

What is the recommended way to monitor latency in production?

Instrument the FastAPI app with Prometheus client middleware. The middleware automatically records request duration histograms, which can be scraped by a Prometheus server and visualized in Grafana.


💡 Want to practise this hands-on? DigitalOcean gives new accounts $200 free credit for 60 days — enough to spin up a full Linux/Docker/Kubernetes environment at no cost.

📚 Recommended reading: Best DevOps & cloud books on Amazon — from Linux fundamentals to Kubernetes in production, curated for working engineers.

📚 References & Further Reading

  • Official FastAPI documentation — covers ASGI server choices and request validation: fastapi.tiangolo.com
  • scikit‑learn model persistence guide — explains joblib serialization format: scikit-learn.org
  • Kubernetes Deployment concepts — details replica management and probes: kubernetes.io

Top comments (0)