Deploy Machine Learning Models with FastAPI and Docker
A model in a notebook helps nobody. Wrap it in a small web API, package it in a container, and any service can request predictions over HTTP.
Train and save the model
Save the whole preprocessing and model pipeline together so training and serving cannot drift apart.
# train.py
import joblib
from sklearn.datasets import load_iris
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
X, y = load_iris(return_X_y=True)
pipe = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500)).fit(X, y)
joblib.dump(pipe, "model.joblib")
print("saved, train accuracy:", round(pipe.score(X, y), 3))Build the FastAPI service
Load the model once at startup, not on every request. Pydantic rejects invalid input automatically with a clear error.
# app.py
from contextlib import asynccontextmanager
import joblib
from fastapi import FastAPI
from pydantic import BaseModel, Field
state = {}
@asynccontextmanager
async def lifespan(app):
state["model"] = joblib.load("model.joblib")
yield
state.clear()
app = FastAPI(title="Iris API", lifespan=lifespan)
NAMES = ["setosa", "versicolor", "virginica"]
class Flower(BaseModel):
sepal_length: float = Field(gt=0, lt=20)
sepal_width: float = Field(gt=0, lt=20)
petal_length: float = Field(gt=0, lt=20)
petal_width: float = Field(gt=0, lt=20)
@app.get("/health")
def health():
return {"status": "ok"}
@app.post("/predict")
def predict(f: Flower):
x = [[f.sepal_length, f.sepal_width, f.petal_length, f.petal_width]]
proba = state["model"].predict_proba(x)[0]
i = int(proba.argmax())
return {"class": NAMES[i], "probability": round(float(proba[i]), 4)}Containerise with Docker
Pin dependency versions and run as a non-root user.
# requirements.txt
fastapi
uvicorn[standard]
scikit-learn
joblib
pydantic
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py model.joblib ./
RUN useradd -m appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]Build, run and test
Use the same scikit-learn version for training and serving, otherwise a saved model may fail to load or behave differently.
docker build -t iris-api .
docker run -p 8000:8000 iris-api
curl -X POST http://localhost:8000/predict \
-H "Content-Type: application/json" \
-d '{"sepal_length":5.1,"sepal_width":3.5,"petal_length":1.4,"petal_width":0.2}'
# {"class":"setosa","probability":0.9...}Production checklist
Add request logging, a /health endpoint for orchestrators, input limits, automated tests, and model version in every response. Run several workers behind a load balancer for traffic, and keep secrets in environment variables, not in the image.
Hands-on practice
Run the examples in the downloadable Jupyter notebook. The extra checks below focus on mistakes that appear in real projects, not just syntax.
Validate request data with Pydantic
from pydantic import BaseModel, Field
class ChurnRequest(BaseModel):
tenure: int = Field(ge=0)
monthly_charges: float = Field(gt=0)
print(ChurnRequest(tenure=12, monthly_charges=79.5).model_dump()){'tenure': 12, 'monthly_charges': 79.5}Schema validation prevents malformed requests reaching the model.
Smoke-test a prediction endpoint
import requests
r = requests.post('http://localhost:8000/predict', json={'tenure':12,'monthly_charges':79.5})
print(r.status_code)
print(r.json())200
{"prediction": 0, "probability": 0.18}A deployment is not done until an external client can call it successfully.
Self-check
- Can you explain why the first practice result is trustworthy rather than just plausible?
- What would you monitor or test before using this FastAPI + Docker workflow in production?
- Which assumption in the example would break first on a larger or messier dataset?
Frequently asked questions
Why FastAPI for ML serving?
It is fast, validates input with type hints and generates interactive API docs at /docs automatically.
Why use Docker for a model?
A container bundles the exact Python version and libraries, so the model behaves the same on your laptop and on a server.
Should I load the model on every request?
No. Load it once at startup. Loading per request is slow and wastes memory.
Primary references
Use these official sources when exact syntax, defaults or definitions matter beyond the examples here.




















































































































































