Deploying a model is where a notebook experiment becomes a usable service. If you want to deploy sklearn model Docker workflows reliably, the practical path is to train a scikit-learn model, save it, expose it through an API, package the API and model into a Docker image, and automate build/test/push steps in CI/CD.
This tutorial uses the same proven stack covered in the source material: scikit-learn for training, joblib or pickle for persistence, FastAPI and Pydantic for request validation, Uvicorn for serving, and Docker for consistent deployment across environments.
What You Need Before Deploying a Scikit-Learn Model
Before you deploy an sklearn model with Docker, make sure the project has four working pieces: a trained model, a prediction interface, a dependency list, and Docker installed locally.
The source examples consistently frame Docker as the solution to a common deployment problem: code that works on one machine may fail elsewhere because of differences in operating systems, package versions, or configuration. Docker packages the application, dependencies, libraries, and settings into a portable container so the service runs consistently across environments.
Key idea: Docker does not replace model training or API design. It packages your trained model, serving code, dependencies, and runtime instructions into a repeatable deployment unit.
Required tools and files
| Requirement | Why you need it | Source-grounded example |
|---|---|---|
| Python | Used to train the model and serve predictions | Source examples use Python with scikit-learn and FastAPI |
| scikit-learn | Trains classical ML models | Examples use RandomForestClassifier, DecisionTreeClassifier, and RandomForestRegressor |
| joblib or pickle | Saves and loads trained models | Sources show joblib.dump, joblib.load, and pickle.dump |
| FastAPI | Exposes the model as a REST API | Sources create /predict or /classify endpoints |
| Pydantic | Validates incoming request bodies | Sources define request schemas with BaseModel |
| Uvicorn | Runs the FastAPI app | Sources run uvicorn app:app or uvicorn main:app |
| Docker | Builds and runs the containerized service | Sources use Dockerfile, docker build, and docker run |
| Docker Hub or container registry | Stores and shares built images | Sources show docker tag, docker push, and docker pull |
Recommended project structure
A clean project layout keeps training code, model artifacts, API code, and container files separate. The source examples use structures with models/, app/, requirements.txt, and Dockerfile.
sklearn-docker-service/
├── app/
│ ├── __init__.py
│ └── main.py
├── models/
│ └── iris_model.joblib
├── train_model.py
├── requirements.txt
├── Dockerfile
└── README.md
Why this structure works:
- Training code:
train_model.pycreates the model artifact. - Model artifact:
models/iris_model.joblibis loaded by the API. - API code:
app/main.pydefines request validation and prediction routes. - Dependencies:
requirements.txtkeeps package installation reproducible. - Container config:
Dockerfiledefines how Docker builds and runs the service.
Train and Save a Scikit-Learn Model
The first step is to train a model and persist it to disk. The sources use small datasets to keep the deployment tutorial focused: the Iris dataset, dummy blob classification data, and the scikit-learn diabetes dataset.
For this tutorial, we’ll use the Iris dataset, which appears in multiple source examples. It has four features:
- Sepal length
- Sepal width
- Petal length
- Petal width
Those features classify observations into three classes: Iris Setosa, Iris Versicolour, and Iris Virginica.
Create train_model.py
# train_model.py
from pathlib import Path
from joblib import dump
from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
MODEL_DIR = Path("models")
MODEL_PATH = MODEL_DIR / "iris_model.joblib"
def train_model():
# Load Iris dataset
iris = datasets.load_iris()
X = iris.data
y = iris.target
# Split dataset into train and test sets
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.3,
random_state=42
)
# Train model
model = RandomForestClassifier(random_state=42)
model.fit(X_train, y_train)
# Evaluate model
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy}")
# Save model
MODEL_DIR.mkdir(exist_ok=True)
dump(model, MODEL_PATH)
print(f"Model saved to {MODEL_PATH}")
if __name__ == "__main__":
train_model()
Run it locally:
python train_model.py
This produces a saved model file:
models/iris_model.joblib
joblib vs pickle for saving sklearn models
The source material shows both joblib and pickle being used to persist scikit-learn models.
| Method | Used in source examples for | Typical role in this tutorial |
|---|---|---|
| joblib | Saving scikit-learn pipelines and classifiers with dump() / load() |
Used here for iris_model.joblib |
| pickle | Saving a trained diabetes model as diabetes_model.pkl |
Valid alternative if your team already uses pickle |
Practical note: Use one persistence method consistently across training and serving. If you save with
joblib.dump(), load withjoblib.load().
Create a Prediction API for the Model
Once the model is saved, the next step is to expose it through an API. The source examples use FastAPI because it supports request validation through Pydantic and automatically generates OpenAPI documentation.
FastAPI also provides interactive documentation at /docs and ReDoc documentation at /redoc, as shown in the source examples.
Install API dependencies
Create a requirements.txt file:
fastapi
uvicorn
pydantic
scikit-learn
joblib
The source examples recommend keeping the dependency list focused instead of blindly freezing every package in your local environment. One source uses a filtered command like this:
pip freeze | grep -E "fastapi|joblib|pydantic|scikit-learn|uvicorn" > requirements.txt
If you use conda, the source example shows exporting selected packages and converting them into pip-style pins:
conda list --export | grep -E "fastapi|joblib|pydantic|scikit-learn|uvicorn" | sed -E "s/(.*)=(.*)=.*$/\1==\2/" > requirements.txt
Create app/main.py
# app/main.py
import os
from pathlib import Path
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel, conlist
from joblib import load
MODEL_PATH = os.getenv("MODEL_PATH", "models/iris_model.joblib")
class IrisRequest(BaseModel):
data: List[conlist(float, min_length=4, max_length=4)]
class PredictionResponse(BaseModel):
prediction: List[int]
probabilities: List[List[float]]
app = FastAPI(
title="Iris ML API",
description="API for serving a scikit-learn Iris classifier",
version="1.0.0"
)
@app.on_event("startup")
def load_model():
model_file = Path(MODEL_PATH)
if not model_file.exists():
raise FileNotFoundError(f"Model file not found: {MODEL_PATH}")
app.model = load(model_file)
@app.get("/health")
def health_check():
return {"status": "ok"}
@app.post("/predict", response_model=PredictionResponse)
def predict(request: IrisRequest):
prediction = app.model.predict(request.data).tolist()
probabilities = app.model.predict_proba(request.data).tolist()
return PredictionResponse(
prediction=prediction,
probabilities=probabilities
)
Why this API shape works
This API follows patterns from the source examples:
- Model loaded at startup: Sources load the model when the FastAPI app starts instead of loading it on every request.
- Pydantic validation: The request schema requires each Iris sample to contain exactly four float values.
- POST endpoint: Sources use
/predictor/classifyas a POST endpoint for inference. - Structured response: Sources return predictions plus probabilities or log probabilities.
- Health endpoint: Added here as a minimal operational endpoint so containers and deployment systems can check whether the API is running.
Important warning: Make sure the feature order in your API matches the feature order used during training. The Iris model expects four values in the same order used by the dataset: sepal length, sepal width, petal length, and petal width.
Run the API locally before Docker
uvicorn app.main:app --host 127.0.0.1 --port 5000
Then open:
http://127.0.0.1:5000/docs
FastAPI should show an interactive Swagger UI where you can test /predict.
You can also test with curl:
curl -X POST "http://127.0.0.1:5000/predict" \
-H "Content-Type: application/json" \
-d '{"data":[[4.8,3.0,1.4,0.3],[6.0,2.9,4.5,1.5]]}'
Expected response shape:
{
"prediction": [0, 1],
"probabilities": [
[1.0, 0.0, 0.0],
[0.0, 0.9, 0.1]
]
}
Your exact probability values may differ depending on the trained model.
Write a Dockerfile for the ML Service
Now that the API works locally, package it with Docker. This is the core step when you want to deploy sklearn model Docker workflows across machines without manually recreating the environment.
The sources show multiple Dockerfile patterns:
| Source pattern | Base image | Runtime command |
|---|---|---|
| Minimal Python app | python:3.11-slim | CMD ["python", "model.py"] |
| FastAPI sklearn service | python:3.8-slim-buster | CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] |
| FastAPI-optimized image | tiangolo/uvicorn-gunicorn:python3.8-slim | Uses FastAPI-oriented environment variables |
For this tutorial, we’ll use a slim Python image and run Uvicorn directly, matching the simple Dockerfile style shown in the sources.
Create Dockerfile
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
COPY app /app/app
COPY models /app/models
ENV MODEL_PATH=models/iris_model.joblib
ENV PORT=80
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT}"]
What each Dockerfile instruction does
| Instruction | Purpose |
|---|---|
| FROM | Specifies the base image. Source examples use slim Python images such as python:3.11-slim and python:3.8-slim-buster. |
| WORKDIR | Sets the working directory inside the container. |
| COPY requirements.txt | Adds the dependency file before the application code. |
| RUN pip install | Installs FastAPI, Uvicorn, scikit-learn, joblib, and Pydantic. |
| COPY app / COPY models | Copies the API code and saved model into the image. |
| ENV | Sets default runtime configuration such as model path and port. |
| CMD | Starts the FastAPI service with Uvicorn. |
Build tip from the source material: Tag the image during build so you do not have to rely on Docker-generated image IDs.
Run and Test the Container Locally
Build the Docker image:
docker build --tag sklearn-iris-api:latest .
Run the container:
docker run -d -p 8000:80 --name sklearn-iris-api sklearn-iris-api:latest
This maps local port 8000 to container port 80, similar to the source examples that run FastAPI containers and expose them through Docker port mapping.
Test the health endpoint:
curl http://localhost:8000/health
Expected response:
{"status":"ok"}
Test the prediction endpoint:
curl -X POST "http://localhost:8000/predict" \
-H "Content-Type: application/json" \
-d '{"data":[[4.8,3.0,1.4,0.3],[6.0,2.9,4.5,1.5]]}'
You should receive a JSON response with predictions and class probabilities.
Useful Docker commands for local testing
| Command | Purpose |
|---|---|
docker build --tag sklearn-iris-api:latest . |
Builds the image from the current directory |
docker run -d -p 8000:80 --name sklearn-iris-api sklearn-iris-api:latest |
Runs the API container in detached mode |
docker logs sklearn-iris-api |
Shows container logs |
docker stop sklearn-iris-api |
Stops the running container |
docker rm sklearn-iris-api |
Removes the stopped container |
Add Environment Variables and Health Checks
The basic source examples often hard-code values such as model paths and ports. For a more deployable service, parameterize them with environment variables and add a health check endpoint.
This is especially useful when the same image moves between local testing, staging, and production-like environments.
Environment variables used in this tutorial
| Variable | Default | Purpose |
|---|---|---|
| MODEL_PATH | models/iris_model.joblib |
Tells the API where to load the model from |
| PORT | 80 |
Sets the port Uvicorn listens on inside the container |
Run with a custom model path:
docker run -d \
-p 8000:80 \
-e MODEL_PATH=models/iris_model.joblib \
--name sklearn-iris-api \
sklearn-iris-api:latest
Add a Docker health check
Docker supports HEALTHCHECK instructions in the Dockerfile. The source material does not provide a health-check implementation, but the API pattern above includes a simple /health route so a container runtime can verify the service is responding.
To use curl inside the health check, the image must include it. A minimal version looks like this:
# Dockerfile with health check support
FROM python:3.11-slim
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
COPY app /app/app
COPY models /app/models
ENV MODEL_PATH=models/iris_model.joblib
ENV PORT=80
HEALTHCHECK CMD curl --fail http://localhost:${PORT}/health || exit 1
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT}"]
Check health status:
docker ps
Look for the container health state in the output.
Operational caution: Keep the health endpoint lightweight. It should confirm the API process is responsive; avoid expensive prediction calls as a basic liveness check.
Set Up a Basic CI/CD Deployment Pipeline
A CI/CD pipeline for this project should automate the same commands you already tested locally: install dependencies, train or verify the model artifact, build the Docker image, run a container test, tag the image, and push it to a registry such as Docker Hub.
The sources specifically show Docker Hub commands:
docker login
docker tag ml-model yourdockerhubusername/ml-model
docker push yourdockerhubusername/ml-model
docker pull yourdockerhubusername/ml-model
docker run yourdockerhubusername/ml-model
They also show tagging during build:
docker build --tag my-name/my-sklearn-api-service:latest .
docker push my-name/my-sklearn-api-service:latest
CI/CD stages for an sklearn Docker deployment
| Stage | What it does | Command pattern |
|---|---|---|
| Install | Installs Python dependencies | pip install -r requirements.txt |
| Train or verify model | Creates or confirms model artifact exists | python train_model.py |
| Build image | Builds Docker image | docker build --tag ... . |
| Run container test | Starts container and checks /health |
docker run ... + curl |
| Prediction smoke test | Sends a sample request to /predict |
curl -X POST ... |
| Tag image | Tags image for registry | docker tag ... |
| Push image | Uploads image to Docker Hub or registry | docker push ... |
Basic CI/CD script
Because CI/CD platforms differ, here is a platform-neutral shell script that can be adapted to your chosen system:
#!/usr/bin/env bash
set -e
IMAGE_NAME="${IMAGE_NAME:-sklearn-iris-api}"
IMAGE_TAG="${IMAGE_TAG:-latest}"
FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"
echo "Installing dependencies..."
pip install -r requirements.txt
echo "Training model..."
python train_model.py
echo "Building Docker image..."
docker build --tag "${FULL_IMAGE}" .
echo "Starting container..."
docker rm -f sklearn-iris-api-test || true
docker run -d -p 8000:80 --name sklearn-iris-api-test "${FULL_IMAGE}"
echo "Waiting briefly for API startup..."
sleep 5
echo "Checking health endpoint..."
curl --fail http://localhost:8000/health
echo "Running prediction smoke test..."
curl --fail -X POST "http://localhost:8000/predict" \
-H "Content-Type: application/json" \
-d '{"data":[[4.8,3.0,1.4,0.3]]}'
echo "Cleaning up test container..."
docker rm -f sklearn-iris-api-test
echo "Pipeline completed successfully."
Optional Docker Hub push step
If your CI/CD environment has registry credentials configured, add:
docker login
docker tag sklearn-iris-api:latest yourdockerhubusername/sklearn-iris-api:latest
docker push yourdockerhubusername/sklearn-iris-api:latest
Then another environment can pull and run it:
docker pull yourdockerhubusername/sklearn-iris-api:latest
docker run -d -p 8000:80 yourdockerhubusername/sklearn-iris-api:latest
CI/CD principle: The deployment pipeline should run the same build and smoke-test steps every time so the Docker image pushed to the registry is known to start and respond.
This is the automation layer that completes a practical deploy sklearn model Docker workflow.
Common Deployment Mistakes and How to Avoid Them
Even a small scikit-learn API can fail in production-like environments if the model, dependencies, or request schema are not handled carefully.
1. Loading the model on every request
The FastAPI source examples load the model during startup rather than inside the prediction function. This avoids repeatedly reading the model file for every inference request.
Avoid it like this:
@app.on_event("startup")
def load_model():
app.model = load("models/iris_model.joblib")
Do not do this inside every request:
@app.post("/predict")
def predict(request: IrisRequest):
model = load("models/iris_model.joblib")
return model.predict(request.data)
2. Sending the wrong number of features
The Iris examples require four features per observation. The source API example uses Pydantic’s constrained lists to enforce exactly four values.
Avoid it like this:
class IrisRequest(BaseModel):
data: List[conlist(float, min_length=4, max_length=4)]
If a client sends only three values, FastAPI validation rejects the request before it reaches the model.
3. Mixing up feature order
A model trained on [sepal length, sepal width, petal length, petal width] expects that same order at prediction time.
Bad request shape:
{
"data": [[1.4, 0.3, 4.8, 3.0]]
}
Correct request shape:
{
"data": [[4.8, 3.0, 1.4, 0.3]]
}
4. Copying too many dependencies into requirements.txt
One source explicitly warns against blindly requiring every package installed on your system. Instead, filter the dependencies to only those needed by the service.
For this tutorial, the focused dependency list is:
fastapi
uvicorn
pydantic
scikit-learn
joblib
5. Forgetting to include the model file in the Docker image
Your API may work locally because models/iris_model.joblib exists on your machine. It will fail in Docker if the file is not copied into the image.
Correct Dockerfile line:
COPY models /app/models
6. Not testing the container before pushing it
The sources verify Docker builds by running the container and calling the API endpoint with curl.
A good minimum test is:
docker run -d -p 8000:80 --name sklearn-iris-api sklearn-iris-api:latest
curl --fail http://localhost:8000/health
curl --fail -X POST "http://localhost:8000/predict" \
-H "Content-Type: application/json" \
-d '{"data":[[4.8,3.0,1.4,0.3]]}'
7. Assuming a notebook is a deployment
The source material emphasizes that a model in a notebook is not yet useful to other systems. To make it usable, expose it as an API and package it with its dependencies.
A deployable service needs:
- Saved artifact: The trained model file.
- API contract: A predictable request and response format.
- Runtime: FastAPI and Uvicorn.
- Container: Docker image with code, model, and dependencies.
- Verification: Local container test and CI/CD smoke test.
Bottom Line
To deploy sklearn model Docker workflows effectively, keep the system simple and repeatable: train the model, save it with joblib or pickle, serve it through FastAPI, validate inputs with Pydantic, containerize it with Docker, and test the running container before pushing it to Docker Hub or another registry.
The strongest pattern across the source material is consistency. Docker packages your ML service and its dependencies into a portable unit, while FastAPI gives you a structured prediction API with automatic documentation at /docs and /redoc.
For a basic CI/CD setup, automate the same commands you trust locally: install dependencies, train or verify the model, build the image, run the container, call /health, smoke-test /predict, tag the image, and push it to a registry.
FAQ
How do I deploy a scikit-learn model with Docker?
Train the model with scikit-learn, save it with joblib or pickle, create a FastAPI prediction endpoint, write a Dockerfile that installs dependencies and copies the model into the image, then build and run the container with docker build and docker run.
Should I use FastAPI for serving a scikit-learn model?
FastAPI is used throughout the source examples because it supports REST endpoints, Pydantic request validation, async-compatible route handlers, and automatically generated API documentation at /docs and /redoc.
Should I save my sklearn model with joblib or pickle?
Both are shown in the source material. joblib is used in examples that save scikit-learn classifiers and pipelines, while pickle is used in an example that saves a diabetes progression model. Use the same tool for saving and loading.
What should be in requirements.txt?
For the FastAPI sklearn service in this tutorial, the dependency file includes fastapi, uvicorn, pydantic, scikit-learn, and joblib. One source recommends filtering dependencies instead of blindly exporting every installed package.
How do I test the Dockerized model locally?
Run the container with a port mapping, then call the health and prediction endpoints:
docker run -d -p 8000:80 sklearn-iris-api:latest
curl http://localhost:8000/health
curl -X POST "http://localhost:8000/predict" \
-H "Content-Type: application/json" \
-d '{"data":[[4.8,3.0,1.4,0.3]]}'
How does CI/CD fit into sklearn Docker deployment?
A basic CI/CD pipeline automates the same steps you run locally: install dependencies, train or verify the model, build the Docker image, run a container test, call the API with curl, tag the image, and push it to Docker Hub or another container registry.









