Train, Track, and Serve a Text Classifier
Build an end-to-end ML pipeline with MLflow, FastAPI, and Docker.
Introduction
30 Second Summary
Every day, machine learning models get trained in notebooks and never make it to production. The gap between "my model works" and "anyone can use my model" is where most ML projects stall.
In this project, you will build a complete ML pipeline that takes a text classifier from training to a live API. You will train the model, track your experiments, serve predictions over HTTP, and package everything in a container.
What You'll Build
A sentiment classifier trained with scikit-learn, tracked with MLflow, served as a REST API with FastAPI, and containerized with Docker.
By the end of this project, you'll have:
- A trained TF-IDF + Logistic Regression text classifier with experiment tracking in MLflow (parameters, metrics, and model artifacts viewable in a dashboard).
- A FastAPI REST API with a /predict endpoint that accepts text and returns sentiment predictions.
- A Docker container packaging your entire application into a portable, deployable service.
- Secret Mission: Deploy your containerized API to AWS EC2 free tier and make it publicly accessible.
Are there any prerequisites?
You should be familiar with Python and basic ML concepts like supervised learning and train/test splits. No prior experience with MLflow, FastAPI, or Docker is needed.
Set Up Your Development Environment
Before you can train models and serve predictions, you need the right tools on your machine. This project relies on Python for machine learning code, Docker for packaging your app into a portable container, and a few key libraries that handle the heavy lifting.
In this step, you will verify your Python installation, install Docker Desktop, and set up an isolated project environment with all the dependencies you need.
In this step, get ready to:
- Verify Python 3.11+ is installed and ready.
- Install Docker Desktop for containerization.
- Create a project folder with a virtual environment and install dependencies.
Verify Python 3.11+ is installed
This project's dependencies (specifically scikit-learn 1.9.0) require Python 3.11 or higher. Let's confirm you have the right version.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- Open the integrated terminal by pressing Ctrl+` (backtick key, top-left of keyboard).
- Check your Python version by running this command:
python --version
macOS users
On macOS, you may need to use python3 --version instead. The python command might not exist or might point to an older system Python.
✔️ I see version 3.11 or higher
You should see output like Python 3.11.x or Python 3.12.x or higher. You are good to go.
ⓧ I see a version lower than 3.11
Your system has an older Python that will not work with this project's dependencies. You need to install Python 3.11 or newer alongside it.
- Visit python.org/downloads and download the latest Python 3 installer for your operating system.
- Run the installer. On Windows, make sure to check Add Python to PATH before clicking Install Now.
- Close and reopen VS Code so the terminal picks up the new Python.
- Run python --version again to confirm you now see 3.11 or higher.
macOS default Python
macOS ships with an older Python by default. After installing from python.org, you may need to use python3.11 or python3 to access the newer version. The installer adds it to your PATH automatically.
ⓧ Command not found
Python is not installed on your system yet.
- Visit python.org/downloads and download the latest Python 3 installer for your operating system.
- Run the installer. On Windows, make sure to check Add Python to PATH before clicking Install Now.
- On macOS, double-click the downloaded .pkg file and follow the prompts.
- Close and reopen VS Code so the terminal picks up the new Python.
- Run python --version (or python3 --version on macOS) to confirm you see 3.11 or higher.
Install Docker Desktop
Docker lets you package your entire application into a container that runs the same way on any machine. You will use it in the final step to containerize your prediction API.
- Check if Docker is already installed by running this command in your VS Code terminal:
docker --version
✔️ I see a version number
You should see something like Docker version 24.x.x or higher. Docker is ready.
- Make sure Docker Desktop is running. Look for the Docker whale icon in your system tray (Windows) or menu bar (macOS).
ⓧ Command not found
Docker is not installed yet. Let's set it up.
Windows
- Visit the Docker Desktop download page and download the installer.
- Double-click Docker Desktop Installer.exe to run it.
- Choose the per-user installation option. This uses the WSL 2 backend and does not require admin rights.
- Follow the prompts to complete installation. Your computer may need to restart.
- After the restart, Docker Desktop launches automatically. Accept the Docker Subscription Service Agreement when prompted.
- Open a new terminal in VS Code and run docker --version to confirm it is installed.
Docker Desktop is free for learning
Docker Desktop is free for personal use, education, and small businesses (fewer than 250 employees and under $10M revenue). You are using it for learning, so no paid subscription is needed.
macOS
- Visit the Docker Desktop download page and download the .dmg file for your chip (Apple Silicon or Intel).
- Double-click Docker.dmg to open the installer, then drag the Docker icon to the Applications folder.
- Open Docker.app from your Applications folder.
- Accept the Docker Subscription Service Agreement when prompted. The first launch may take a few minutes.
- Open a new terminal in VS Code and run docker --version to confirm it is installed.
Docker Desktop is free for learning
Docker Desktop is free for personal use, education, and small businesses (fewer than 250 employees and under $10M revenue). You are using it for learning, so no paid subscription is needed.
Docker command still not found after install?
Make sure Docker Desktop is actually running (look for the whale icon in your system tray or menu bar). If you just installed it, close and reopen VS Code so the terminal refreshes its PATH.
help me troubleshoot Docker installation
Create your project and install dependencies
Now that your tools are ready, you will create a dedicated folder for this project and set up a Python virtual environment. A virtual environment keeps this project's packages isolated from your system Python.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir ml-pipeline
cd ml-pipeline
- Create a Python virtual environment by running:
python -m venv .venv
Use python3 on macOS
If python does not work on your system, substitute python3 in all commands throughout this project.
- Activate the virtual environment by running:
macOS
source .venv/bin/activate
Windows
.venv\Scripts\activate
You should see (.venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.
- Install the three core dependencies by running:
pip install mlflow==3.12.0 "fastapi[standard]"==0.136.3 scikit-learn==1.9.0
What are these packages?
MLflow is the industry-standard tool for tracking ML experiments. It logs your model's parameters, metrics, and artifacts so you can compare runs.
FastAPI is a modern Python web framework that auto-generates API documentation and uses type hints for request validation.
scikit-learn provides the machine learning algorithms (TF-IDF vectorization and Logistic Regression) you will use to build your text classifier.
The installation takes a minute or two. When it finishes, you should see a success message for all three packages.
Installation failing?
If you see errors about Python version compatibility, confirm you are running 3.11 or higher inside your virtual environment with python --version. The (.venv) prefix must be visible in your prompt.
help me fix my pip install errors for mlflow, fastapi, or scikit-learn
- Verify your setup by running:
python -c "import mlflow; import fastapi; import sklearn; print('All packages installed successfully')"
You should see All packages installed successfully printed in the terminal.
Your development environment is locked and loaded. Next up, you will write a training script that builds a sentiment classifier and logs every detail to MLflow.
Train a Sentiment Classifier with Experiment Tracking
Training a machine learning model is only half the job. Without a record of what parameters you used, what accuracy you achieved, and which model file was produced, you cannot reproduce or compare experiments.
In this step, you will train a text classifier using scikit-learn and track every detail of the experiment with MLflow. By the end, you will have a trained model and a dashboard showing exactly how it performed.
In this step, get ready to:
- Create a training script that loads a text dataset and builds a classification pipeline.
- Add experiment tracking that logs parameters, metrics, and the trained model artifact.
- Run the training and explore results in the MLflow dashboard.
Create the training script and load the dataset
The 20 Newsgroups dataset is a classic text classification benchmark built into scikit-learn. You will use four of its categories to keep training fast while still having a meaningful classification task.
- In VS Code, create a new file called train.py in your project folder.
- Paste the following code into train.py:
import mlflow
from mlflow.models import infer_signature
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, f1_score
import numpy as np
# Configure MLflow
mlflow.set_tracking_uri("http://127.0.0.1:5000")
mlflow.set_experiment("sentiment-classifier")
# Load dataset (using a subset of categories for faster training)
categories = ["sci.space", "rec.sport.baseball", "comp.graphics", "talk.politics.guns"]
newsgroups = fetch_20newsgroups(subset="all", categories=categories, remove=("headers", "footers", "quotes"))
X_train, X_test, y_train, y_test = train_test_split(
newsgroups.data, newsgroups.target, test_size=0.2, random_state=42
)
What does this code do?
- mlflow.set_tracking_uri("http://127.0.0.1:5000") tells MLflow to send experiment data to the local tracking server you will start shortly.
- mlflow.set_experiment("sentiment-classifier") creates a named experiment to group related runs together.
- fetch_20newsgroups downloads a dataset of newsgroup posts already labeled by topic. You load four categories to keep training quick.
- train_test_split reserves 20% of the data for testing so you can measure how well the model generalizes.
Next, define the hyperparameters and build the classification pipeline. Add this code below what you just pasted:
# Define hyperparameters
params = {
"tfidf__max_features": 5000,
"clf__C": 1.0,
"clf__max_iter": 1000,
}
# Build pipeline
pipeline = Pipeline([
("tfidf", TfidfVectorizer(max_features=params["tfidf__max_features"])),
("clf", LogisticRegression(C=params["clf__C"], max_iter=params["clf__max_iter"], random_state=42)),
])
What does this code do?
The params dictionary stores hyperparameters in one place so MLflow can log them easily later.
The Pipeline chains two steps together. First, TF-IDF converts raw text into numerical features by measuring how important each word is. Then Logistic Regression uses those features to predict which category a text belongs to.
- Save train.py.
You will not run this file yet. The training code goes inside the MLflow tracking block you will add next.
Add MLflow experiment tracking
MLflow's tracking API lets you wrap a training run so that every parameter, metric, and model artifact is recorded automatically. This means you can compare experiments later without digging through terminal output.
- In train.py, add the following code below the pipeline definition:
# Train with MLflow tracking
with mlflow.start_run():
mlflow.log_params(params)
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred, average="weighted")
mlflow.log_metric("accuracy", accuracy)
mlflow.log_metric("f1_score", f1)
signature = infer_signature(X_train[:1], pipeline.predict(X_train[:1]))
model_info = mlflow.sklearn.log_model(
sk_model=pipeline,
name="text_classifier",
signature=signature,
input_example=X_train[:1],
)
mlflow.set_tag("model_type", "tfidf_logistic_regression")
mlflow.set_tag("dataset", "20newsgroups_4categories")
print(f"Accuracy: {accuracy:.4f}")
print(f"F1 Score: {f1:.4f}")
print(f"Model URI: {model_info.model_uri}")
print(f"Run ID: {mlflow.active_run().info.run_id}")
What does this code do?
- with mlflow.start_run(): opens a tracked experiment run. Everything inside this block is logged to MLflow.
- mlflow.log_params(params) records the hyperparameters so you can see exactly what settings produced each result.
- pipeline.fit(X_train, y_train) trains the model on the training data.
- mlflow.log_metric saves accuracy and F1 score. These are the numbers you will compare across experiments.
- mlflow.sklearn.log_model() saves the entire trained pipeline as a reusable model artifact. The signature records what input/output shapes the model expects.
- mlflow.set_tag adds searchable metadata to the run.
- Save train.py.
✔️ Awesome, I've got everything!
Great. Double check you have saved train.py before continuing.
ⓧ I'd like to double check the full code
import mlflow
from mlflow.models import infer_signature
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, f1_score
import numpy as np
# Configure MLflow
mlflow.set_tracking_uri("http://127.0.0.1:5000")
mlflow.set_experiment("sentiment-classifier")
# Load dataset (using a subset of categories for faster training)
categories = ["sci.space", "rec.sport.baseball", "comp.graphics", "talk.politics.guns"]
newsgroups = fetch_20newsgroups(subset="all", categories=categories, remove=("headers", "footers", "quotes"))
X_train, X_test, y_train, y_test = train_test_split(
newsgroups.data, newsgroups.target, test_size=0.2, random_state=42
)
# Define hyperparameters
params = {
"tfidf__max_features": 5000,
"clf__C": 1.0,
"clf__max_iter": 1000,
}
# Build pipeline
pipeline = Pipeline([
("tfidf", TfidfVectorizer(max_features=params["tfidf__max_features"])),
("clf", LogisticRegression(C=params["clf__C"], max_iter=params["clf__max_iter"], random_state=42)),
])
# Train with MLflow tracking
with mlflow.start_run():
mlflow.log_params(params)
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred, average="weighted")
mlflow.log_metric("accuracy", accuracy)
mlflow.log_metric("f1_score", f1)
signature = infer_signature(X_train[:1], pipeline.predict(X_train[:1]))
model_info = mlflow.sklearn.log_model(
sk_model=pipeline,
name="text_classifier",
signature=signature,
input_example=X_train[:1],
)
mlflow.set_tag("model_type", "tfidf_logistic_regression")
mlflow.set_tag("dataset", "20newsgroups_4categories")
print(f"Accuracy: {accuracy:.4f}")
print(f"F1 Score: {f1:.4f}")
print(f"Model URI: {model_info.model_uri}")
print(f"Run ID: {mlflow.active_run().info.run_id}")
Run training and explore the MLflow dashboard
The training script sends experiment data to the MLflow tracking server. You need to start that server first, then run the script in a separate terminal.
- In your VS Code terminal, start the MLflow tracking server by running:
mlflow server --port 5000
What does this command do?
This starts a local MLflow tracking server on port 5000. It uses SQLite as the default backend to store your experiment data. The server stays running in this terminal until you stop it.
The server will keep this terminal busy, so you need a second terminal to run the training script.
- Open a new terminal in VS Code by pressing Ctrl+Shift+` (macOS and Windows).
- Activate your virtual environment in the new terminal by running:
.venv/Scripts/activate
- Run the training script:
python train.py
The script will download the dataset on first run, then train the model. After a few seconds, you should see output showing accuracy, F1 score, model URI, and run ID printed in the terminal.
What should I see?
Your terminal output will look something like this (exact numbers may vary slightly):
Accuracy: 0.9280
F1 Score: 0.9278
Model URI: runs:/<run-id>/text_classifier
Run ID: abc123def456...
An accuracy around 0.92 means the model correctly classifies about 92% of unseen text. That is a strong result for a simple pipeline.
- Open your browser and navigate to http://localhost:5000.
- Click on the sentiment-classifier experiment in the left sidebar.
- Click on the run to see logged parameters, metrics, and the model artifact.
Why is experiment tracking valuable?
Without tracking, you would need to manually record which hyperparameters produced which accuracy score. MLflow does this automatically and lets you compare runs side-by-side.
In a team setting, everyone shares the same tracking server. This means anyone can see what experiments have been tried and which model performs best.
MLflow UI not loading?
- Make sure the MLflow server is still running in your first terminal. If you see an error or the process stopped, run mlflow server --port 5000 again.
- Check that you are visiting http://localhost:5000 (not https).
- If you see a connection refused error when running train.py, the server was not running before the script executed. Start the server first, then re-run the script.
help me troubleshoot MLflow server connection issues
Your model is trained and tracked. Next up, you will build a REST API that loads this model and serves live predictions.
Build a Prediction API with FastAPI
Your model is trained and tracked. Now you need a way for other applications to use it.
In this step, you will build a REST API using FastAPI that loads your trained model from MLflow and serves predictions over HTTP. Anyone who sends text to your API will get back the predicted category and a confidence score.
In this step, get ready to:
- Create a FastAPI application that loads the trained model on startup.
- Define a prediction endpoint that accepts text and returns classified results.
- Test the API locally using FastAPI's auto-generated interactive docs.
Create the FastAPI application with model loading
FastAPI needs your code structured as a Python package. This means creating an app/ directory with an __init__.py file (which tells Python this folder is a package) and a main.py file for your API code.
- Create the app directory inside your project folder by running this command:
mkdir app
- In the VS Code Explorer sidebar, right-click the new app folder and select New File.
- Name the file __init__.py and leave it empty.
- Right-click the app folder again and create another new file named main.py.
- Confirm the files exist by running:
ls app
You should see __init__.py and main.py in the output.
Don't see both files?
Make sure you created the files inside the app folder, not in the project root. The __init__.py file must be present for Python to treat app/ as a package.
Help me set up my FastAPI project structure.
- Open app/main.py in VS Code and paste the following code to set up the application skeleton:
import mlflow
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="ML Text Classifier API", version="1.0.0")
# Load model on startup
mlflow.set_tracking_uri("http://127.0.0.1:5000")
model = None
class PredictionRequest(BaseModel):
text: str
class PredictionResponse(BaseModel):
predicted_category: str
confidence: float
CATEGORY_NAMES = ["comp.graphics", "rec.sport.baseball", "sci.space", "talk.politics.guns"]
@app.on_event("startup")
def load_model():
global model
model = mlflow.pyfunc.load_model("models:/text_classifier/latest")
@app.get("/")
def root():
return {"message": "ML Text Classifier API is running", "docs": "/docs"}
What does this code do?
- mlflow.set_tracking_uri("http://127.0.0.1:5000") connects to the MLflow server you started in the previous step, so the app can load your registered model.
- PredictionRequest and PredictionResponse are Pydantic models that define the exact shape of incoming requests and outgoing responses. FastAPI uses them to validate data automatically.
- The @app.on_event("startup") decorator tells FastAPI to run load_model() once when the server starts. This loads the model into memory a single time rather than on every request.
- CATEGORY_NAMES maps the integer predictions from the model back to human-readable category names.
You won't see a change yet. The next chunk adds the prediction logic that makes the API useful.
Define the prediction endpoint
The /predict endpoint is where the real work happens. It takes text input, runs it through your trained model, and returns both the predicted category and the model's confidence in that prediction.
- Add the following code at the bottom of app/main.py, below the root() function:
@app.post("/predict", response_model=PredictionResponse)
def predict(request: PredictionRequest):
prediction = model.predict([request.text])
predicted_index = int(prediction[0])
predicted_category = CATEGORY_NAMES[predicted_index]
# Get confidence from the underlying sklearn model
sklearn_model = model.unwrap_python_model()
probabilities = sklearn_model.predict_proba([request.text])
confidence = float(probabilities[0][predicted_index])
return PredictionResponse(
predicted_category=predicted_category,
confidence=round(confidence, 4),
)
@app.get("/health")
def health():
return {"status": "healthy", "model_loaded": model is not None}
What does the prediction logic do?
The predict() function wraps the text in a list (because scikit-learn expects array-like input), passes it through the full TF-IDF + Logistic Regression pipeline, and converts the integer output to a category name.
To get the confidence score, the code unwraps the underlying scikit-learn model and calls predict_proba() which returns the probability for each class. It picks the probability that matches the predicted category.
The /health endpoint is a quick check that reports whether the model loaded successfully.
- Save app/main.py.
✔️ Awesome, I've got everything!
Great. Double check you have saved app/main.py before moving on.
ⓧ I'd like to double check the full code
Here is the complete app/main.py file. Make sure your code matches this exactly:
import mlflow
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="ML Text Classifier API", version="1.0.0")
# Load model on startup
mlflow.set_tracking_uri("http://127.0.0.1:5000")
model = None
class PredictionRequest(BaseModel):
text: str
class PredictionResponse(BaseModel):
predicted_category: str
confidence: float
CATEGORY_NAMES = ["comp.graphics", "rec.sport.baseball", "sci.space", "talk.politics.guns"]
@app.on_event("startup")
def load_model():
global model
model = mlflow.pyfunc.load_model("models:/text_classifier/latest")
@app.get("/")
def root():
return {"message": "ML Text Classifier API is running", "docs": "/docs"}
@app.post("/predict", response_model=PredictionResponse)
def predict(request: PredictionRequest):
prediction = model.predict([request.text])
predicted_index = int(prediction[0])
predicted_category = CATEGORY_NAMES[predicted_index]
# Get confidence from the underlying sklearn model
sklearn_model = model.unwrap_python_model()
probabilities = sklearn_model.predict_proba([request.text])
confidence = float(probabilities[0][predicted_index])
return PredictionResponse(
predicted_category=predicted_category,
confidence=round(confidence, 4),
)
@app.get("/health")
def health():
return {"status": "healthy", "model_loaded": model is not None}
Test the API locally
Your MLflow tracking server is still running in the terminal from the previous step. You need a second terminal to start the FastAPI development server.
- Open a new terminal in VS Code by clicking Terminal in the top menu bar, then New Terminal.
- Activate your virtual environment in the new terminal by running:
.venv\Scripts\activate
- Start the FastAPI development server by running:
fastapi dev app/main.py
What should I see?
The terminal will display a startup banner showing the server address at http://127.0.0.1:8000 and the documentation URL at http://127.0.0.1:8000/docs. You will also see a line that says Application startup complete which confirms the model loaded successfully.
- Open your browser and navigate to http://127.0.0.1:8000/docs.
- Click the POST /predict section to expand it.
- Click Try it out.
- Replace the request body content with this JSON:
{
"text": "NASA launched a new spacecraft to explore Mars"
}
- Click Execute.
You should see a JSON response with a predicted_category of sci.space and a confidence score close to 1.0.
Seeing a connection error or 500 response?
If the server fails to start with a connection error, make sure your MLflow tracking server is still running in the other terminal at http://127.0.0.1:5000. The FastAPI app needs it to load the model.
If you see a 500 error when calling /predict, check your terminal for traceback details. The most common cause is a typo in CATEGORY_NAMES or a missing import.
Help me fix my FastAPI prediction endpoint.
Your model is now serving live predictions over HTTP. Next up, you will package this entire application into a Docker container so it can run anywhere.
Containerize with Docker
You have a working API on your machine. The next challenge is making it portable. Right now, anyone who wants to run your prediction service needs Python, your exact dependencies, and access to your MLflow server.
Docker solves this by packaging your application, its dependencies, and the trained model into a single container that runs identically on any machine. In this step, you will containerize your FastAPI prediction service so it can be deployed anywhere.
In this step, get ready to:
- Create a requirements file and Dockerfile for your project.
- Export your trained model and build a Docker image.
- Run and verify the containerized API.
Create the requirements file and Dockerfile
A Dockerfile is a recipe that tells Docker how to build your container image. It needs to know which dependencies to install and which files to copy in.
- In VS Code, create a new file called requirements.txt in your project root folder (the same level as train.py).
- Paste the following content into requirements.txt:
mlflow==3.12.0
fastapi[standard]==0.136.3
scikit-learn==1.9.0
What does this file do?
This file pins the exact versions of your three dependencies. Docker uses it during the image build to install the same packages you have locally. Pinning versions ensures the container behaves identically to your development environment.
- Save requirements.txt.
- Now create another new file called Dockerfile (no file extension) in the same project root folder.
- Paste the following content into your Dockerfile:
FROM python:3.14
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
COPY ./mlflow_model /code/mlflow_model
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
What does this Dockerfile do?
- FROM python:3.14 starts from the official Python base image.
- WORKDIR /code sets the working directory inside the container.
- COPY ./requirements.txt and RUN pip install installs dependencies first. This layer gets cached so rebuilds are fast when only your code changes.
- COPY ./app and COPY ./mlflow_model bring in your FastAPI application and the trained model artifact.
- CMD runs your FastAPI app on port 80 when the container starts.
- Save the Dockerfile.
File not recognized as a Dockerfile?
Make sure the file is named exactly Dockerfile with a capital D and no file extension. If VS Code added .txt automatically, rename it.
help me fix my Dockerfile naming
Export the model and build the Docker image
The Dockerfile expects a mlflow_model directory containing your trained model. You need to export it from MLflow into your project folder so Docker can package it into the container.
- Export your trained model from MLflow by running this command in your VS Code terminal:
mlflow artifacts download -u "models:/text_classifier/latest" -d mlflow_model
What does this command do?
This uses the MLflow CLI to download your registered model artifact to a local directory called mlflow_model. The -u flag specifies the model URI in the registry. The -d flag sets the destination folder.
- Confirm the export worked by running:
ls mlflow_model
You should see files like MLmodel, model.pkl, and requirements.txt inside the directory.
Why export the model separately?
In production, you want your container to be self-contained. Bundling the model artifact directly into the Docker image means the container can serve predictions without needing a live connection to an MLflow tracking server. This is a common production pattern for ML model deployment.
- Now build your Docker image by running:
docker build -t ml-api .
What does this command do?
The -t ml-api flag tags (names) your image. The . tells Docker to look for the Dockerfile in the current directory. Docker reads each instruction and builds layers, caching them for fast rebuilds.
The build takes a minute or two as Docker downloads the Python image and installs your dependencies. You should see output ending with a success message and the image ID.
Build failing with file not found errors?
Make sure you are running the command from your project root folder (the one containing Dockerfile, requirements.txt, and the app/ folder). Also confirm that mlflow_model/ exists from the previous export step.
help me debug my Docker build errors
Run and verify the containerized API
Your image is built. Time to launch it as a running container and confirm that predictions work through Docker.
- Start the container by running:
docker run -d --name ml-api -p 80:80 ml-api
What does this command do?
- -d runs the container in detached mode (in the background).
- --name ml-api gives the container a friendly name you can reference later.
- -p 80:80 maps port 80 on your machine to port 80 inside the container.
- The last ml-api is the image name you tagged during the build.
- Verify the container is running by sending a test prediction request:
curl -X POST http://localhost/predict -H "Content-Type: application/json" -d "{\"text\": \"The spacecraft launched into orbit successfully\"}"
You should see a JSON response with a predicted category and confidence score, similar to:
{"predicted_category":"sci.space","confidence":0.9231}
What should I see?
The response confirms your entire pipeline is working end-to-end inside Docker. The predicted_category should be sci.space for space-related text. The confidence value will vary slightly but should be high (above 0.8).
Use the interactive docs instead
If you prefer a visual interface, open http://localhost/docs in your browser. FastAPI's auto-generated documentation lets you test the /predict endpoint directly from the page.
Connection refused on localhost?
Check that the container is actually running with docker ps. If it exited immediately, view the logs with docker logs ml-api to see what went wrong during startup.
If you see a model loading error in the logs, make sure your MLflow tracking server is still running at http://127.0.0.1:5000 and that the mlflow_model directory was properly exported in the previous substep.
help me debug my Docker container not starting
✔️ Awesome, I've got everything!
Great. Double check you have saved all your files.
ⓧ I'd like to double check the full code
Here is the complete requirements.txt:
mlflow==3.12.0
fastapi[standard]==0.136.3
scikit-learn==1.9.0
Here is the complete Dockerfile:
FROM python:3.14
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
COPY ./mlflow_model /code/mlflow_model
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
Your ML prediction service is now running inside a Docker container. You have gone from training a model to serving it as a production-ready, containerized API. This is the exact workflow that ML engineers use to ship models to production.
Secret mission
Deploy to AWS EC2 Free Tier
Your API works locally and in Docker. But a model sitting on your laptop can't serve predictions to the world. In this secret mission, you'll deploy your containerized API to a real cloud server and make it publicly accessible over the internet.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs unless you completed the Secret Mission. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Cost warning
If you completed the Secret Mission and deployed to EC2, your t2.micro instance is covered by the AWS Free Tier (750 hours/month for the first 12 months). After that, or if you exceed 750 hours, you will be charged. Terminate the instance if you are not actively using it.
Resources you used:
- Docker container ml-api running locally on port 80.
- Docker image ml-api stored locally.
- Project folder on your Desktop containing source files, trained model, and MLflow data.
- EC2 t2.micro instance (Secret Mission only).
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with your ML pipeline.
- Your Docker container continues serving predictions on port 80.
- Docker Desktop remains installed for future projects.
- If you deployed to EC2, your instance continues running and counting against your Free Tier hours.
Pause - I'll come back to this later
Stop running processes to free up memory and compute, but keep your files and images for later.
- Stop the local Docker container by running:
- If you deployed to EC2, stop (not terminate) the instance to avoid charges while preserving it. Open the EC2 console, select your instance, choose Instance state, then Stop instance.
docker stop ml-api
When you are ready to resume, run:
docker start ml-api
Delete - I don't want to use this again
Remove all project resources and start fresh. This is permanent.
- Stop and remove the Docker container by running:
docker stop ml-api
docker rm ml-api
- Remove the Docker image by running:
docker rmi ml-api
- Delete your project folder from the Desktop by running:
cd ~/Desktop
rm -rf ml-pipeline
What about the MLflow server?
The MLflow tracking server was a local process that stopped when you closed the terminal. Deleting the project folder removes the SQLite database and all experiment artifacts.
- If you completed the Secret Mission, terminate your EC2 instance. Open the EC2 console at https://console.aws.amazon.com/ec2/.
- In the navigation pane, choose Instances.
- Select your instance, then choose Instance state, then Terminate (delete) instance.
- Choose Terminate (delete) when prompted for confirmation.
What about Docker Desktop?
Docker Desktop is a general-purpose tool useful for many projects. You do not need to uninstall it unless you want to reclaim disk space.
If you do want to remove it on Windows: open Settings, select Apps, then Apps & features. Find Docker Desktop in the list and select Uninstall.
That's a Wrap!
That's a Wrap!
Nice work! You just built something most ML learners never finish: a complete training-to-production pipeline that takes a model from raw data to a publicly deployable service.
You've learned how to:
- Trained a text classifier using scikit-learn's TF-IDF + Logistic Regression pipeline and tracked experiments with MLflow (parameters, metrics, and model artifacts) viewable in a live dashboard.
- Built a FastAPI REST API with auto-generated interactive docs that serves real predictions from your trained model.
- Containerized the entire application with Docker for portable deployment to any environment.
- Deployed the containerized API to AWS EC2, making your model publicly accessible over the internet.
Ready to quiz yourself?