Build an End-to-End MLOps Pipeline

Train a diabetes prediction model and serve it as a containerized REST API.

Introduction

30 Second Summary

Most machine learning models never make it past a Jupyter notebook. The gap between a working model and a production system that other people can actually use is where most ML projects stall.

In this project, you will build an end-to-end MLOps pipeline that trains a diabetes prediction model, tracks every experiment, versions the data, and serves predictions through a containerized API.

What You'll Build

A complete MLOps pipeline that predicts diabetes from patient health metrics, backed by versioned data, tracked experiments, and a containerized REST API.

By the end of this project, you'll have:

  • A DVC-tracked dataset with version history, so you always know which data trained which model.
  • An MLflow experiment with logged parameters, metrics, and model artifacts viewable in a web UI.
  • A FastAPI prediction endpoint packaged inside a Docker container, ready to deploy anywhere.
  • Secret Mission: Compare multiple models in MLflow and automatically serve the best one.

Are there any prerequisites?

You should be comfortable with basic Python (variables, functions, imports) and have used a command line before. No prior experience with MLOps tools is needed.

Set Up Your Environment

Every ML project needs a clean, isolated environment to avoid dependency conflicts. This is especially important in MLOps, where you're coordinating multiple tools that all need to play nicely together.

In this step, you'll create a dedicated project folder, set up a Python virtual environment, initialize version control with Git, and install all the packages your pipeline needs.

In this step, get ready to:
  • Create a project directory with a virtual environment and Git repository.
  • Install the ML and MLOps packages your pipeline needs.
  • Verify all tools are working correctly.
Create a project directory and initialize a Python virtual environment and Git repository

A virtual environment keeps your project's packages separate from your system Python. Combined with Git for version control, this gives you a reproducible starting point that you can share or rebuild at any time.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Antigravity and press Enter to open Google Antigravity IDE.

What is Google Antigravity IDE?

Google Antigravity IDE is a VS Code fork with AI agent capabilities. It has an integrated terminal, Python support, and works the same way as VS Code. It's free during public preview.

  • Open the integrated terminal by pressing Ctrl+` (backtick key, usually above Tab).

Before creating your project, you need Git installed for version control. Check if it's available by running:

  • Check whether Git is installed by running this command:
git --version

✔️ I see a version number

Git is ready to go. Move on to the next instruction below.

ⓧ Command not found

You need to install Git. Visit git-scm.com/downloads and download the installer for your operating system.

  • Run the installer and accept the default options.
  • Close and reopen your terminal in Google Antigravity IDE (press Ctrl+` twice).
  • Run git --version again to confirm it works.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir diabetes-mlops
cd diabetes-mlops
  • Confirm the folder exists by running:
ls

The folder will be empty for now. That's expected.

  • Create a Python virtual environment by running this command:
python -m venv venv
  • Activate the virtual environment:

macOS

source venv/bin/activate

Windows

venv\Scripts\activate

You should see (venv) appear at the start of your terminal prompt. This confirms the virtual environment is active.

Don't see (venv) in your prompt?

On Windows, if you see an error about execution policies, run Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned in PowerShell first, then try activating again.

help me activate my Python virtual environment

  • Initialize a Git repository by running this command:
git init

You should see a message like Initialized empty Git repository in your terminal output.

  • Open your project folder in the IDE by clicking File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the diabetes-mlops folder, and click Open.

You should see diabetes-mlops in the Explorer sidebar on the left, with a venv folder visible inside it.

Why a virtual environment and Git together?

The virtual environment isolates your Python packages so they don't conflict with other projects on your machine. Git tracks every change you make to your code.

Together, they let you reproduce your exact project state at any point in time. This is the foundation of reproducibility in MLOps.

Install the ML and MLOps packages

Your pipeline needs six packages. MLflow handles experiment tracking, DVC versions your data, scikit-learn trains the model, and FastAPI serves predictions as an API.

  • Open a new terminal in Google Antigravity IDE by pressing Ctrl+`.
  • Make sure you see (venv) in your prompt. If not, activate it again using the command from earlier.
  • Install all packages by running this command:
pip install mlflow==3.12.0 dvc scikit-learn==1.8.0 fastapi==0.136.3 uvicorn==0.34.2 pandas==2.2.3

What are all these packages?

  • mlflow tracks experiments (parameters, metrics, and trained models).
  • dvc versions large data files without bloating your Git repository.
  • scikit-learn provides the machine learning algorithms you'll train.
  • fastapi builds the REST API that serves predictions.
  • uvicorn is the server that runs your FastAPI application.
  • pandas handles data loading and manipulation.

This installation may take 1-2 minutes. You should see a Successfully installed message with a list of packages when it finishes.

Seeing installation errors?

Make sure (venv) is showing in your terminal prompt. Installing without an active virtual environment can cause permission errors.

If you see a network error, check your internet connection and try running the command again.

help me fix pip install errors in my MLOps project

Now you'll create a requirements.txt file that records the packages needed to run your API in production. This is what Docker will use later to rebuild the environment inside a container.

  • In the Explorer sidebar, click the new file icon (or press Ctrl+N).
  • Name the file requirements.txt.
  • Paste the following contents into the file:
mlflow==3.12.0
scikit-learn==1.8.0
fastapi==0.136.3
uvicorn==0.34.2
pandas==2.2.3

Why is DVC not in requirements.txt?

DVC is a development tool you use locally to version your data. It's not needed inside the Docker container that serves predictions. Keeping requirements.txt lean means faster container builds and smaller images.

  • Save requirements.txt by pressing Ctrl+S.

Next, you'll create a .gitignore file that tells Git which files to skip. You don't want to commit your virtual environment or raw data files to the repository.

  • Create another new file in the Explorer sidebar.
  • Name it .gitignore.
  • Paste the following contents into the file:
__pycache__/
*.pyc
venv/
data/diabetes.csv

What does each line do?

  • __pycache__/ and *.pyc ignore Python's compiled bytecode files.
  • venv/ ignores the virtual environment folder (it's too large for Git).
  • data/diabetes.csv ignores the raw dataset. DVC will handle versioning this file instead of Git.
  • Save .gitignore by pressing Ctrl+S.

You should now see both requirements.txt and .gitignore in the Explorer sidebar.

✔️ Awesome, I've got everything!

Great. Double check you've saved both files before continuing.

ⓧ I'd like to double check the full code

Here's what requirements.txt should look like:

mlflow==3.12.0
scikit-learn==1.8.0
fastapi==0.136.3
uvicorn==0.34.2
pandas==2.2.3

Here's what .gitignore should look like:

__pycache__/
*.pyc
venv/
data/diabetes.csv
Verify all tools are working

Before moving on, confirm that all your newly installed tools respond correctly. This catches any installation issues now, before they cause confusing errors later.

  • In your terminal, verify MLflow is installed by running:
mlflow --version
  • Verify DVC is installed by running:
dvc --version
  • Verify scikit-learn is importable by running:
python -c "import sklearn; print(sklearn.__version__)"

✔️ All three commands show version numbers

Your environment is fully set up. You should see mlflow reporting version 3.12.0, dvc reporting its version, and scikit-learn printing 1.8.0.

ⓧ One or more commands failed

  • Make sure (venv) is showing in your terminal prompt. If not, activate it again.
  • Try reinstalling by running the pip install command from earlier.
  • If dvc --version fails specifically, try closing and reopening your terminal, then activating the venv again.

help me troubleshoot my MLOps tool installation

Your environment is ready. You have a clean project folder with version control, isolated Python packages, and all the MLOps tools you need. Next up, you'll version your dataset with DVC so every experiment is traceable back to exactly which data it used.

Version Your Data with DVC

Your environment is ready. Now you need data to train your model on.

In real ML projects, datasets change constantly. Rows get added, labels get corrected, features get engineered. Without tracking these changes, you end up asking "which version of the data produced this model?" DVC (Data Version Control) solves this by tracking large data files alongside Git without storing them directly in your repository.

In this step, get ready to:
  • Download the Pima Indians Diabetes dataset.
  • Initialize DVC and track the dataset file.
  • Commit the DVC tracking files to Git.
Download the Pima Indians Diabetes dataset

The Pima Indians Diabetes dataset is a classic binary classification dataset with 768 patient records and 8 health features. It is small enough to train instantly on a CPU, making it perfect for focusing on the infrastructure around the model.

  • Create the data/ directory by running this command:
mkdir data
  • Confirm the directory exists by running:
ls

You should see data listed alongside your other project files.

  • Create a download script by opening a new file called download_data.py in the editor, then paste in the following code:
import urllib.request

# Download the Pima Indians Diabetes dataset from GitHub
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.csv"
urllib.request.urlretrieve(url, "data/diabetes.csv")

# Add column headers to make the CSV self-documenting
headers = "Pregnancies,Glucose,BloodPressure,SkinThickness,Insulin,BMI,DiabetesPedigreeFunction,Age,Outcome"
with open("data/diabetes.csv", "r") as f:
    data = f.read()
with open("data/diabetes.csv", "w") as f:
    f.write(headers + "\n" + data)

print("Dataset downloaded: 768 rows, 9 columns")

What does this script do?

  • urllib.request.urlretrieve downloads the raw CSV file from a public GitHub repository into your data/ folder.
  • The raw file has no column headers, so the second half of the script reads the file back in and prepends a header row with descriptive column names.
  • These column names (Pregnancies, Glucose, BMI, etc.) match what your training script expects later.
  • Save download_data.py and run it by executing this command:
python download_data.py

You should see the message Dataset downloaded: 768 rows, 9 columns printed in your terminal.

  • Verify the dataset loaded correctly by running this quick check:
python -c "import pandas as pd; df = pd.read_csv('data/diabetes.csv'); print(df.shape); print(list(df.columns))"

You should see (768, 9) followed by all nine column names.

Seeing a connection error?

Make sure you have an internet connection. The script downloads the dataset from GitHub.

If your network blocks raw GitHub URLs, download the file manually from https://github.com/jbrownlee/Datasets/blob/master/pima-indians-diabetes.csv using the download button, save it into your data/ folder, and re-run the script (it will still add the headers).

Help me debug my dataset download

Initialize DVC and track the dataset

Now that your data is in place, you need to tell DVC to start managing it. This is a two-command process: initialize DVC in the project, then point it at the file to track.

  • Initialize DVC in your project by running this command:
dvc init

What does dvc init do?

This creates a hidden .dvc/ directory in your project root. It stores DVC's internal configuration, cache, and tracking metadata. Think of it like git init but for data files.

  • Track the diabetes dataset with DVC by running this command:
dvc add data/diabetes.csv

What did dvc add just create?

DVC created two new files:

  • data/diabetes.csv.dvc is a small YAML file that records the dataset's hash, size, and path. This is the file you commit to Git instead of the raw data.
  • data/.gitignore tells Git to ignore data/diabetes.csv so the actual data never enters your Git history.

The real CSV stays in DVC's local cache. Git only ever sees the lightweight .dvc tracking file.

  • Confirm the tracking files were created by running:
ls data/

You should see diabetes.csv, diabetes.csv.dvc, and .gitignore in the output.

Don't see the .dvc file?

Make sure you ran dvc init before dvc add. DVC needs to be initialized in the project first.

Also confirm that data/diabetes.csv actually exists. If the download failed earlier, DVC has nothing to track.

Help me troubleshoot my DVC setup

Commit the tracking files to Git

The final piece is committing your DVC configuration and tracking files to Git. This is the key insight: Git stores the small pointer file (.dvc), while DVC stores the actual data. Anyone who clones your repo can recreate the exact dataset by running dvc pull.

  • Stage and commit all DVC-related files by running these commands:
git add .dvc/ data/diabetes.csv.dvc data/.gitignore
git commit -m "Initialize DVC and track diabetes dataset"

What is being committed here?

  • .dvc/ contains DVC's internal configuration files.
  • data/diabetes.csv.dvc is the pointer file that records which version of the data you are using.
  • data/.gitignore ensures the raw CSV stays out of Git.

The actual diabetes.csv file (768 rows of patient data) is NOT in this commit. It lives only in DVC's cache. This keeps your Git repository lightweight no matter how large your datasets grow.

  • Verify the commit succeeded by running:
git log --oneline

You should see your new commit message Initialize DVC and track diabetes dataset at the top of the log.

Git says there are untracked files?

If you see warnings about untracked files, that is fine. Files like download_data.py and requirements.txt are not part of this commit. You can commit them later or add them to .gitignore.

If the commit itself failed, check that you ran git init in Step 1. Also make sure you are in the diabetes-mlops directory.

Help me fix my git commit

Your dataset is now versioned. If the data ever changes, a simple dvc add and git commit captures the new version. Next up, you will train a model on this data and track the experiment with MLflow.

Train and Track Your Model with MLflow

Your data is versioned and your environment is ready. Now it's time to train your first model and track everything about the experiment so you can reproduce it later.

In a real ML team, the question is never "can you train a model?" It's "can you tell me exactly what you did to get that accuracy score?" MLflow answers that question by automatically logging every parameter, metric, and model artifact from your training run.

In this step, get ready to:
  • Write a training script that trains a Random Forest classifier on the diabetes dataset.
  • Run the script and observe MLflow automatically logging everything.
  • Launch the MLflow UI and explore your experiment results visually.
Write the training script

The training script handles loading data, splitting it into training and test sets, training a Random Forest classifier, and logging everything to MLflow. You'll build it in chunks.

  • Create a new file called train.py in your project root (the diabetes-mlops folder) by right-clicking in the Explorer sidebar and selecting New File.
  • Add the imports and MLflow setup by copying this code into train.py:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
import mlflow
import mlflow.sklearn

# Set up MLflow experiment
mlflow.set_experiment("diabetes-prediction")

# Enable autologging for scikit-learn
mlflow.sklearn.autolog()

What does this code do?

mlflow.set_experiment("diabetes-prediction") creates (or selects) a named experiment. All runs you do next will be grouped under this experiment.

mlflow.sklearn.autolog() tells MLflow to automatically log parameters, metrics, models, and signatures for any scikit-learn model you train. You don't have to manually record every hyperparameter.

  • Next, add the data loading and splitting logic below the autolog line:
# Load the dataset
column_names = [
    "Pregnancies", "Glucose", "BloodPressure", "SkinThickness",
    "Insulin", "BMI", "DiabetesPedigreeFunction", "Age", "Outcome"
]
data = pd.read_csv("data/diabetes.csv", names=column_names)

# Split features and target
X = data.drop("Outcome", axis=1)
y = data["Outcome"]

# Train-test split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

What does this code do?

The CSV has no header row, so column_names assigns human-readable names to each of the 8 features plus the Outcome target column (1 = diabetic, 0 = healthy).

train_test_split reserves 20% of the data for testing. The random_state=42 ensures the split is identical every time you run the script.

  • Finally, add the model training and evaluation block below the train-test split:
# Train the model with MLflow tracking
with mlflow.start_run(run_name="random_forest"):
    # Define hyperparameters
    params = {
        "n_estimators": 100,
        "max_depth": 5,
        "random_state": 42,
    }

    # Train model
    model = RandomForestClassifier(**params)
    model.fit(X_train, y_train)

    # Evaluate
    y_pred = model.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    precision = precision_score(y_test, y_pred)
    recall = recall_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred)

    # Log additional metrics manually
    mlflow.log_metric("test_accuracy", accuracy)
    mlflow.log_metric("test_precision", precision)
    mlflow.log_metric("test_recall", recall)
    mlflow.log_metric("test_f1", f1)

    print(f"Accuracy: {accuracy:.3f}")
    print(f"Precision: {precision:.3f}")
    print(f"Recall: {recall:.3f}")
    print(f"F1 Score: {f1:.3f}")

What does this code do?

mlflow.start_run(run_name="random_forest") opens a tracking context. Everything inside the with block gets recorded as part of this named run.

Autologging captures the hyperparameters and model artifact automatically. The manual mlflow.log_metric() calls add your custom test-set metrics on top of what autolog already records.

Why both autolog and manual metrics?

Autologging captures training-time metrics that scikit-learn computes internally. Your manual test_accuracy, test_precision, test_recall, and test_f1 metrics evaluate performance on the held-out test set. This is what you actually care about when comparing models later.

  • Save train.py.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's the complete train.py for comparison:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
import mlflow
import mlflow.sklearn

# Set up MLflow experiment
mlflow.set_experiment("diabetes-prediction")

# Enable autologging for scikit-learn
mlflow.sklearn.autolog()

# Load the dataset
column_names = [
    "Pregnancies", "Glucose", "BloodPressure", "SkinThickness",
    "Insulin", "BMI", "DiabetesPedigreeFunction", "Age", "Outcome"
]
data = pd.read_csv("data/diabetes.csv", names=column_names)

# Split features and target
X = data.drop("Outcome", axis=1)
y = data["Outcome"]

# Train-test split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# Train the model with MLflow tracking
with mlflow.start_run(run_name="random_forest"):
    # Define hyperparameters
    params = {
        "n_estimators": 100,
        "max_depth": 5,
        "random_state": 42,
    }

    # Train model
    model = RandomForestClassifier(**params)
    model.fit(X_train, y_train)

    # Evaluate
    y_pred = model.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    precision = precision_score(y_test, y_pred)
    recall = recall_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred)

    # Log additional metrics manually
    mlflow.log_metric("test_accuracy", accuracy)
    mlflow.log_metric("test_precision", precision)
    mlflow.log_metric("test_recall", recall)
    mlflow.log_metric("test_f1", f1)

    print(f"Accuracy: {accuracy:.3f}")
    print(f"Precision: {precision:.3f}")
    print(f"Recall: {recall:.3f}")
    print(f"F1 Score: {f1:.3f}")
Run the training script

With the script saved, it's time to train your model. MLflow will capture everything in the background.

  • Run the training script in your terminal by running this command:
python train.py

You should see output showing the four evaluation metrics, similar to this:

Accuracy: 0.753
Precision: 0.698
Recall: 0.569
F1 Score: 0.627

What just happened behind the scenes?

MLflow created a new mlruns/ directory in your project root. Inside it, MLflow stored your experiment's parameters, metrics, and the serialized model artifact.

Autologging also captured the model's signature (input and output schema) so that downstream tools know exactly what data format the model expects.

  • Confirm the mlruns/ directory was created by running:
ls mlruns

You should see numbered folders inside (these represent your experiment and its runs).

Seeing a ModuleNotFoundError?

Make sure your virtual environment is active. You should see (venv) at the start of your terminal prompt. If it's missing, activate it again before rerunning the script.

Also check that you're running the command from the diabetes-mlops folder, not a parent directory.

Why is my training script throwing a ModuleNotFoundError?

Launch the MLflow UI and explore your experiment

MLflow includes a web-based dashboard where you can visually explore your logged experiments. This is where MLOps becomes tangible.

  • Launch the MLflow UI by running this command:
mlflow ui

What does this command do?

This starts a local web server on port 5000 that reads the mlruns/ directory and renders your experiments in a visual dashboard. The terminal will stay occupied while the server runs.

  • Open your browser and navigate to http://127.0.0.1:5000.
  • Click on the diabetes-prediction experiment in the left sidebar.
  • Click on the random_forest run to see its details.

You should see your logged parameters (n_estimators, max_depth), metrics (test_accuracy, test_f1, etc.), and a saved model artifact all displayed in the UI.

What is the model artifact?

MLflow serialized your trained Random Forest model into a format that can be loaded later for predictions. This artifact includes the model file, its dependencies, and the input/output signature. You'll use this artifact in the next step to serve predictions via an API.

Page not loading at port 5000?

Make sure the mlflow ui command is still running in your terminal. If you accidentally closed it, run it again.

If another process is using port 5000, you'll see an error. Close the conflicting process or run mlflow ui --port 5001 to use a different port.

Why can't I access the MLflow UI in my browser?

Your model is trained, tracked, and visible in a production-grade experiment dashboard. Next up, you'll take that trained model and serve it as a live REST API that anyone can send prediction requests to.

Serve Your Model with FastAPI

Your model is trained and tracked in MLflow. But a model sitting in a local directory can't help anyone make decisions.

In this step, you'll wrap your trained model in a FastAPI web server that accepts patient data and returns diabetes predictions over HTTP. This turns your experiment into something other applications (or people) can actually call.

In this step, get ready to:
  • Write a FastAPI application that loads your trained model from MLflow.
  • Run the API server locally and test a prediction request.
  • Add a health check endpoint and verify the complete API works end-to-end.
Write a FastAPI application that loads the trained model

FastAPI is a Python web framework designed for building APIs. You'll create a single file that loads your model from MLflow at startup, then exposes an endpoint that accepts patient health metrics and returns a diabetes prediction.

  • In Google Antigravity IDE, create a new file called app.py in your diabetes-mlops folder.
  • Add the imports, app creation, Pydantic data models, and model loading code by copying the following into app.py:
import mlflow
import mlflow.sklearn
import pandas as pd
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(title="Diabetes Prediction API")


class PatientData(BaseModel):
    Pregnancies: float
    Glucose: float
    BloodPressure: float
    SkinThickness: float
    Insulin: float
    BMI: float
    DiabetesPedigreeFunction: float
    Age: float


class PredictionResponse(BaseModel):
    prediction: int
    probability: float
    status: str


# Load the latest model from MLflow
mlflow.set_experiment("diabetes-prediction")
experiment = mlflow.get_experiment_by_name("diabetes-prediction")
runs = mlflow.search_runs(experiment_ids=[experiment.experiment_id], order_by=["metrics.test_accuracy DESC"])
best_run_id = runs.iloc[0]["run_id"]
model = mlflow.sklearn.load_model(f"runs:/{best_run_id}/model")

What does this code do?

The PatientData class defines the 8 health metrics your API expects in each request. PredictionResponse defines what the API sends back. FastAPI uses these Pydantic models to automatically validate incoming data and generate documentation.

The model loading section queries MLflow for all runs in the diabetes-prediction experiment, sorted by accuracy (highest first). It grabs the best run's ID and loads the trained model artifact. This code runs once when the server starts.

You won't see any output yet. The next chunk adds the prediction endpoint that makes this app usable.

  • Add the /predict endpoint below the model loading code in app.py:

@app.post("/predict", response_model=PredictionResponse)
def predict(patient: PatientData):
    input_data = pd.DataFrame([patient.model_dump()])
    prediction = model.predict(input_data)[0]
    probability = model.predict_proba(input_data)[0][1]

    return PredictionResponse(
        prediction=int(prediction),
        probability=float(probability),
        status="diabetic" if prediction == 1 else "healthy",
    )

What does this endpoint do?

The @app.post("/predict") decorator creates a POST endpoint at /predict. When a request arrives, FastAPI automatically parses the JSON body into a PatientData object.

The function converts the patient data into a DataFrame (the format scikit-learn expects), runs model.predict() to get the binary prediction, and model.predict_proba() to get the probability. It returns a structured response with the prediction, probability, and a human-readable status.

  • Save app.py.

The file is ready but the server isn't running yet. In the next substep, you'll start it and send your first prediction request.

Run the API server locally and test it with a sample prediction

Time to bring your API to life. You'll start the server with uvicorn (the ASGI server included in your dependencies) and send a test request.

  • Open a new terminal in Google Antigravity IDE by pressing Ctrl+Shift+`.
  • Make sure your virtual environment is active (you should see (venv) in your prompt). If not, activate it by running:
venv\Scripts\activate
  • Start the FastAPI server by running this command:
uvicorn app:app --reload

What does this command do?

This tells uvicorn to run the app variable (your FastAPI instance) from the app module (your app.py file). The --reload flag makes the server restart automatically whenever you save changes to the code.

You should see output that includes a line like Uvicorn running on http://127.0.0.1:8000. This means your API is live and listening for requests.

Server won't start?

  • If you see a "ModuleNotFoundError", make sure your virtual environment is active (look for (venv) in your terminal prompt).
  • If you see "Address already in use", another process is on port 8000. Close it or run with --port 8001 instead.
  • If you see an MLflow-related error, confirm that your mlruns/ directory exists in your project folder. It was created when you ran train.py in the previous step.

Help me debug my FastAPI server startup error.

  • Open a second terminal tab (press Ctrl+Shift+` again) so you can send requests while the server keeps running.
  • Activate the virtual environment in this terminal too:
venv\Scripts\activate
  • Test the /predict endpoint by sending a sample patient's data with this command:
curl -X POST http://127.0.0.1:8000/predict -H "Content-Type: application/json" -d "{\"Pregnancies\": 6, \"Glucose\": 148, \"BloodPressure\": 72, \"SkinThickness\": 35, \"Insulin\": 0, \"BMI\": 33.6, \"DiabetesPedigreeFunction\": 0.627, \"Age\": 50}"

What should I see?

You should get a JSON response with three fields: prediction (0 or 1), probability (a decimal between 0 and 1), and status (either "diabetic" or "healthy"). The exact values depend on your trained model's output.

Getting a connection error?

  • Make sure the uvicorn server is still running in your first terminal tab. If you closed it accidentally, run uvicorn app:app --reload again.
  • On Windows, if curl isn't recognized, try curl.exe instead.

Help me fix my curl request to the FastAPI prediction endpoint.

Add a /health endpoint and verify the complete API

Production APIs need a health check endpoint. Monitoring tools and load balancers ping this endpoint to confirm the service is alive and which model it's serving.

  • Switch back to app.py in the editor.
  • Find the line that says @app.post("/predict", response_model=PredictionResponse).
  • Add the following /health endpoint directly above that line:

@app.get("/health")
def health_check():
    return {"status": "healthy", "model_run_id": best_run_id}

What does this endpoint do?

The /health endpoint returns the server's status and the MLflow run ID of the loaded model. This lets you quickly verify which model version is being served without sending a full prediction request.

  • Save app.py. The server auto-reloads because of the --reload flag.
  • In your second terminal, test the health endpoint by running:
curl http://127.0.0.1:8000/health

You should see a JSON response with "status": "healthy" and a model_run_id string matching your MLflow experiment run.

  • Confirm the full API works end-to-end by sending one more prediction request:
curl -X POST http://127.0.0.1:8000/predict -H "Content-Type: application/json" -d "{\"Pregnancies\": 1, \"Glucose\": 85, \"BloodPressure\": 66, \"SkinThickness\": 29, \"Insulin\": 0, \"BMI\": 26.6, \"DiabetesPedigreeFunction\": 0.351, \"Age\": 31}"

This request uses lower-risk values. You should see a response with a lower probability and likely a "healthy" status.

Not seeing the /health endpoint?

  • Check that you placed the /health function above the /predict function in your file. Save app.py again.
  • Look at your first terminal (where uvicorn is running) for any error messages. A syntax error prevents the reload from completing.

Help me debug why my /health endpoint isn't responding.

✔️ Awesome, I've got everything!

Great. Double check you've saved app.py and both endpoints are responding.

ⓧ I'd like to double check the full code

Here's the complete app.py file. Make sure yours matches:

import mlflow
import mlflow.sklearn
import pandas as pd
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(title="Diabetes Prediction API")


class PatientData(BaseModel):
    Pregnancies: float
    Glucose: float
    BloodPressure: float
    SkinThickness: float
    Insulin: float
    BMI: float
    DiabetesPedigreeFunction: float
    Age: float


class PredictionResponse(BaseModel):
    prediction: int
    probability: float
    status: str


# Load the latest model from MLflow
mlflow.set_experiment("diabetes-prediction")
experiment = mlflow.get_experiment_by_name("diabetes-prediction")
runs = mlflow.search_runs(experiment_ids=[experiment.experiment_id], order_by=["metrics.test_accuracy DESC"])
best_run_id = runs.iloc[0]["run_id"]
model = mlflow.sklearn.load_model(f"runs:/{best_run_id}/model")


@app.get("/health")
def health_check():
    return {"status": "healthy", "model_run_id": best_run_id}


@app.post("/predict", response_model=PredictionResponse)
def predict(patient: PatientData):
    input_data = pd.DataFrame([patient.model_dump()])
    prediction = model.predict(input_data)[0]
    probability = model.predict_proba(input_data)[0][1]

    return PredictionResponse(
        prediction=int(prediction),
        probability=float(probability),
        status="diabetic" if prediction == 1 else "healthy",
    )

Your trained model is now serving live predictions over HTTP. Next up, you'll package this entire application into a Docker container so it can run anywhere.

Containerize with Docker

Your FastAPI prediction API works perfectly on your machine. But what happens when you want to run it on a colleague's laptop or deploy it to a cloud server? Every machine has different Python versions, different packages, and different configurations.

Docker solves this by packaging your application, its dependencies, and its runtime environment into a single portable unit called a container. In this step, you'll containerize your diabetes prediction API so it can run identically anywhere.

In this step, get ready to:
  • Write a Dockerfile that packages your FastAPI app with all dependencies.
  • Build the Docker image and run it as a container.
  • Test the containerized API by sending a prediction request.
Write a Dockerfile for your API

A Dockerfile is a recipe that tells Docker exactly how to build your application's container image. It specifies the base operating system, installs your dependencies, copies your code, and defines the command to start your app.

  • In Google Antigravity IDE, create a new file called Dockerfile in the root of your diabetes-mlops folder (the same level as app.py and requirements.txt).
  • Paste the following code into Dockerfile:
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .
COPY mlruns/ ./mlruns/

EXPOSE 8000

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

What does this Dockerfile do?

  • FROM python:3.11-slim starts from a lightweight Python 3.11 image as the base operating system.
  • COPY requirements.txt . and RUN pip install installs your project dependencies inside the container.
  • COPY app.py . and COPY mlruns/ ./mlruns/ copies your API code and the trained model artifacts into the container.
  • EXPOSE 8000 documents which port the container listens on.
  • CMD defines the startup command that runs uvicorn when the container launches.

Why copy mlruns/ into the container?

Your app.py loads the trained model from MLflow's local storage (the mlruns/ directory). Without copying this directory into the container, the API would fail to start because it cannot find the model artifact.

  • Save Dockerfile.
  • Confirm the file exists by running:
ls Dockerfile

You should see Dockerfile printed in the output.

Don't see the file?

  • Make sure the file is named exactly Dockerfile with a capital D and no file extension.
  • Verify it is in the root diabetes-mlops folder, not inside a subfolder.

Still stuck? Help me find my Dockerfile in the project structure.

Build the Docker image and run the container

With the Dockerfile written, you can now build an image (a snapshot of your packaged app) and run it as a live container.

  • If you still have the local uvicorn server running from the previous step, press Ctrl+C to stop it. Port 8000 must be free for the container.
  • Build your Docker image by running this command:
docker build -t diabetes-api .

What does this command do?

  • docker build reads the Dockerfile and executes each instruction to create an image.
  • -t diabetes-api tags (names) the image so you can reference it later.
  • . tells Docker to look for the Dockerfile in the current directory.

The build takes a minute or two as Docker downloads the Python base image and installs your pip packages. You should see each step complete with a checkmark or hash.

  • Once the build finishes, start the container by running:
docker run -p 8000:8000 diabetes-api

What does -p 8000:8000 mean?

The -p 8000:8000 flag maps port 8000 on your machine to port 8000 inside the container. Without this, the container's API would be unreachable from your browser or terminal.

You should see uvicorn startup output indicating the server is running on port 8000. This means your containerized API is live.

Seeing a port conflict error?

  • This means something else is already using port 8000. Make sure you stopped the local uvicorn server with Ctrl+C before running the container.
  • If the build step failed, check that Docker Desktop is running. You can verify by looking for the Docker whale icon in your system tray.

Need more help? Help me troubleshoot my Docker build or run error.

Test the containerized API

Your container is running, but you need to confirm it actually serves predictions. The docker run command is occupying your current terminal, so you need a second terminal to send test requests.

  • Open a new terminal panel in Google Antigravity IDE by clicking Terminal in the top menu bar, then New Terminal.
  • Test the health endpoint by running:
curl http://localhost:8000/health

You should see a JSON response with "status": "healthy" and a model_run_id value.

  • Send a prediction request to the containerized API by running:

macOS

curl -X POST http://localhost:8000/predict \
  -H "Content-Type: application/json" \
  -d '{"Pregnancies": 6, "Glucose": 148, "BloodPressure": 72, "SkinThickness": 35, "Insulin": 0, "BMI": 33.6, "DiabetesPedigreeFunction": 0.627, "Age": 50}'

Windows

curl.exe -X POST http://localhost:8000/predict -H "Content-Type: application/json" -d "{\"Pregnancies\": 6, \"Glucose\": 148, \"BloodPressure\": 72, \"SkinThickness\": 35, \"Insulin\": 0, \"BMI\": 33.6, \"DiabetesPedigreeFunction\": 0.627, \"Age\": 50}"

What should I see?

You should get a JSON response with three fields: prediction (0 or 1), probability (a decimal between 0 and 1), and status (either "diabetic" or "healthy"). The exact values depend on the trained model's output for this patient sample.

Getting 'Connection refused'?

  • Switch back to the first terminal tab and check the container is still running. You should see uvicorn logs.
  • On Windows, make sure you are using curl.exe instead of curl. PowerShell aliases curl to a different command that may not work the same way.
  • Verify Docker Desktop is still running and hasn't paused the container.

Still stuck? Help me connect to my running Docker container.

✔️ Awesome, I've got everything!

Great. Double check you've saved your Dockerfile and that both the /health and /predict endpoints returned valid JSON from the container.

ⓧ I'd like to double check the full code

Here is the complete Dockerfile for comparison:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .
COPY mlruns/ ./mlruns/

EXPOSE 8000

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

Your diabetes prediction API is now fully containerized. Anyone with Docker installed can run this image and get identical results, regardless of their local Python version or installed packages. You've completed the full MLOps pipeline from versioned data to a deployable container.

Secret mission

Compare Models and Auto-Select the Best

Your pipeline trains one model. But real ML teams try multiple algorithms and let the data decide. Can you add a second model, compare both in MLflow, and wire your API to automatically serve the winner?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Project folder on your Desktop (contains train.py, app.py, Dockerfile, mlruns/, venv/, and data/).
  • Docker container and image built during Step 5.
  • Git repository and DVC tracking (inside the project folder).

Keep everything running

No action needed. Choose this if you're still actively building or want to continue experimenting with the model.

  • Your Docker container may still be running in the background. It uses minimal resources but occupies port 8000.
  • The mlruns/ folder stores your MLflow experiment data. Keep it to preserve your tracked runs.
  • Your virtual environment (venv/) holds all installed packages. It takes up disk space but is ready to use anytime.

Pause - I'll come back to this later

Shut down running processes to free up memory and ports. Your files stay on disk so you can pick up where you left off.

  • Stop the Docker container by running:
  • If the FastAPI server is still running in a terminal, press Ctrl+C to stop it.
  • Deactivate your virtual environment by running:
docker ps

Find the container running your diabetes prediction image, then stop it:

docker stop <container_id>

Deactivate the virtual environment:

deactivate

When you come back, reactivate your virtual environment and start the server again.

Delete - I don't want to use this again

Remove all project resources and start fresh. This permanently deletes your trained models, experiment history, and project code.

  • Stop and remove the Docker container:
docker ps -a

Find your container ID from the list, then run:

docker stop <container_id>
docker rm <container_id>
  • Remove the Docker image:
docker images

Find your diabetes prediction image, then run:

docker rmi <image_id>
  • Delete the project folder. On macOS/Linux:
cd ~/Desktop
rm -rf <your-project-folder>

On Windows:

cd ~\Desktop
rmdir /s /q <your-project-folder>

Replace <your-project-folder> with the name of the folder you created in Step 1.

Nice Work!

Nice Work!

You just built what most ML Engineers build on their first week at a new job. You have a complete MLOps pipeline that takes a diabetes prediction model from raw data all the way to a containerized API, with every experiment tracked and every dataset versioned.

Here's what you accomplished:

  • Versioned your dataset with DVC so you always know exactly which data trained which model.
  • Tracked experiments with MLflow including parameters, metrics, and model artifacts viewable in a web UI.
  • Served live predictions through a FastAPI REST API, then packaged the entire service in a Docker container ready to deploy anywhere.
  • Secret Mission: Trained a second model (Gradient Boosting), compared both in MLflow, and configured the API to auto-select the best model based on tracked accuracy metrics.

Ready to quiz yourself?