AI and Machine Learning

Real-World MLOps Pipeline: From Notebook to Production

Master MLOps. Learn to build an end-to-end real-world ML pipeline from local notebook experimentation to production deployment, monitoring, and retraining. Code & architecture included.

Khader Vali September 26, 2026 16 min read

As a senior software engineer and technical writer, I’ve seen firsthand the chasm that often exists between a brilliant machine learning model prototyped in a Jupyter notebook and a robust, reliable, and scalable ML system running in production. This chasm is precisely what MLOps aims to bridge. It’s not just about deploying a model; it’s about building an entire ecosystem that supports the continuous lifecycle of machine learning applications.

In this comprehensive guide, we’ll peel back the layers of MLOps, moving beyond high-level definitions to construct a detailed, real-world MLOps pipeline. We’ll start from the initial spark of an idea in a local notebook, navigate through structured development, automated CI/CD, production deployment, and finally, robust monitoring and retraining strategies. My goal is to equip you with the knowledge, architectural patterns, and code examples to confidently take your ML projects from experimental curiosity to impactful production systems.

The MLOps Challenge: From Notebook to Production Pains

The journey from an exploratory notebook to a production-ready ML system is fraught with unique challenges that traditional software development often doesn’t adequately address. A data scientist can achieve impressive metrics on a local dataset, but replicating that performance, ensuring reliability, and maintaining it over time in a dynamic production environment requires a fundamentally different approach.

Here’s why the “notebook to production” gap is so pronounced:

  • Environment Inconsistencies: A notebook’s environment might be difficult to reproduce, leading to “works on my machine” syndrome when moving to a shared or production environment.
  • Lack of Reproducibility: Without proper versioning of data, code, and models, it’s nearly impossible to reproduce past results or understand why a model’s performance changed.
  • Data Drift & Concept Drift: Real-world data changes over time. Input features can shift (data drift), or the relationship between inputs and outputs can change (concept drift), leading to model performance degradation.
  • Manual Processes: Manual steps for data preprocessing, model training, evaluation, and deployment are error-prone, slow, and don’t scale.
  • Lack of Monitoring: Once deployed, an ML model is often treated as a black box. Without active monitoring, performance degradation can go unnoticed for extended periods.
  • Team Collaboration: Data scientists, ML engineers, and software engineers often use different tools and workflows, making collaboration and handoffs challenging.
  • Scaling Inference: Deploying a single model is one thing; scaling it to handle millions of requests per second with low latency is another.
  • Debugging & Rollbacks: Diagnosing issues in a complex ML pipeline and rolling back to a stable version can be difficult without structured processes.

MLOps, drawing inspiration from DevOps principles, introduces practices and tools to address these challenges systematically. It’s about automating, monitoring, and standardizing the entire ML lifecycle to ensure continuous delivery, integration, and deployment of ML systems.

Core Components of a Robust MLOps Pipeline

Before diving into the pipeline stages, let’s understand the essential building blocks. An effective MLOps setup integrates several specialized tools and practices:

1. Data Versioning & Management

Data is the lifeblood of ML. Just as code changes, so does data. You need to version datasets to ensure reproducibility and track lineage. Tools like DVC (Data Version Control), along with cloud object storage (AWS S3, Google Cloud Storage, Azure Blob Storage), are crucial here. A feature store can also centralize and manage features for both training and inference.

2. Experiment Tracking

During experimentation, data scientists run countless trials, tweaking hyperparameters, datasets, and model architectures. An experiment tracking system logs these runs, their parameters, metrics, and artifacts (models, plots). MLflow, Weights & Biases, and Neptune.ai are popular choices.

3. Model Training & Versioning

Training involves executing code on specific data to produce a model artifact. This process needs to be robust, potentially distributed, and reproducible. The resulting model artifact itself needs to be versioned and stored securely, typically in a model registry.

4. Model Registry

A central repository for managing the lifecycle of ML models. It allows for versioning, metadata storage, stage transitions (e.g., staging to production), and approval workflows. MLflow Model Registry, Sagemaker Model Registry, and Azure ML Model Registry are common examples.

5. CI/CD for ML (Continuous Integration/Continuous Delivery)

This is the automation backbone. CI ensures code quality and correctness, while CD automates the training, evaluation, and deployment of models. Tools like GitHub Actions, GitLab CI/CD, Jenkins, Argo Workflows, and specialized MLOps platforms like Kubeflow Pipelines facilitate this.

6. Model Deployment

Serving the model for inference. This can be via REST APIs for real-time predictions, batch processing jobs for offline predictions, or even edge deployments. Technologies like Docker, Kubernetes, FastAPI/Flask, AWS SageMaker Endpoints, Azure ML Endpoints, and Google Cloud AI Platform are used here.

7. Monitoring & Alerting

Post-deployment, continuous monitoring is critical. This includes system health (latency, error rates), model performance (accuracy, precision, recall), and data quality (drift detection). Tools like Prometheus, Grafana, ELK stack, and custom drift detection libraries are essential.

8. Reproducibility & Governance

The ability to recreate any past experiment or model deployment. This involves linking specific versions of code, data, environment, and hyper-parameters. Governance ensures compliance, auditing, and responsible AI practices.

<

Real-World MLOps Pipeline: From Notebook to Production
Generated Image

>

Phase 1: Local Development & The Notebook Realm

Every great ML project often begins in the comfort of a Jupyter Notebook. It’s an unparalleled environment for rapid iteration, data exploration, and model prototyping. Here, the focus is on discovery and proving concepts.

Initial Data Exploration & Feature Engineering

This stage involves loading data, understanding its distribution, identifying missing values, performing initial cleaning, and crafting features that might be useful for the model. Pandas, NumPy, Matplotlib, and Seaborn are your best friends here.


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# Load dataset (e.g., simulated transaction data for fraud detection)
df = pd.read_csv('data/raw_transactions.csv')

# Initial exploration
print(df.head())
print(df.info())
print(df.describe())

# Feature Engineering Example: creating aggregated features
df['transaction_hour'] = pd.to_datetime(df['timestamp']).dt.hour
df['amount_per_customer_avg'] = df.groupby('customer_id')['amount'].transform('mean')
df['risk_score'] = df['transaction_hour'] * 0.1 + df['amount'] / df['amount_per_customer_avg']

# Basic visualization
sns.histplot(df['risk_score'], bins=50)
plt.title('Distribution of Risk Score')
plt.show()

# Split data
X = df.drop(['is_fraud', 'timestamp'], axis=1) # Assuming 'is_fraud' is target
y = df['is_fraud']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# Apply scaling
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

Model Prototyping & Experiment Tracking (Early)

You’ll experiment with various algorithms (e.g., Logistic Regression, Random Forest, Gradient Boosting) and fine-tune hyperparameters. Even at this early stage, it’s wise to start tracking experiments. MLflow is excellent for this, even locally. It helps organize your runs, parameters, and metrics.


import mlflow
import mlflow.sklearn
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score

# Set up MLflow tracking (local file store)
mlflow.set_tracking_uri("file:///path/to/mlruns")
mlflow.set_experiment("fraud_detection_prototype")

with mlflow.start_run(run_name="RandomForest_v1"):
    # Log parameters
    n_estimators = 100
    max_depth = 10
    mlflow.log_param("n_estimators", n_estimators)
    mlflow.log_param("max_depth", max_depth)

    # Train model
    model = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth, random_state=42)
    model.fit(X_train_scaled, y_train)

    # Make predictions
    y_pred = model.predict(X_test_scaled)

    # Log metrics
    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)

    mlflow.log_metric("accuracy", accuracy)
    mlflow.log_metric("precision", precision)
    mlflow.log_metric("recall", recall)
    mlflow.log_metric("f1_score", f1)

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

    # Log model artifact (optional, could be done later more formally)
    mlflow.sklearn.log_model(model, "random_forest_model")

Phase 2: Transition to Structured Development

Once a promising model architecture and feature set emerge from the notebook chaos, it’s time to bring order. This phase focuses on making the code modular, reproducible, and ready for automation.

Refactoring Notebooks into Modular Scripts

Break down the monolithic notebook into distinct, reusable Python scripts:

  • data_preparation.py: Handles data loading, cleaning, and feature engineering.
  • model_training.py: Contains the model definition, training loop, and evaluation logic.
  • model_inference.py: Provides functions for making predictions with a trained model.
  • config.py: Stores parameters, paths, and environment variables.

This improves readability, testability, and maintainability.

Version Control for Code (Git)

Every piece of code—from data preprocessing scripts to model training logic—must be under version control (e.g., Git, hosted on GitHub, GitLab, or Bitbucket). This allows for tracking changes, collaboration, and easy rollbacks.

Environment Management (Docker)

To ensure consistency across development, staging, and production environments, containerization with Docker is paramount. A Dockerfile specifies all dependencies and configurations needed to run your ML code.


# Dockerfile for ML Training Environment
FROM python:3.9-slim-buster

WORKDIR /app

# Install system dependencies if any (e.g., for specific ML libraries)
# RUN apt-get update && apt-get install -y --no-install-recommends \
#     libgomp1 \
#     && rm -rf /var/lib/apt/lists/*

# Copy requirements file and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Command to run training script (can be overridden)
CMD ["python", "src/train.py"]

# requirements.txt
pandas==1.5.3
scikit-learn==1.2.2
numpy==1.24.3
mlflow==2.10.1
fastapi==0.109.0
uvicorn==0.27.0.post1
# Add other dependencies as needed

Data Versioning (DVC)

DVC works alongside Git to version large datasets and machine learning models. It stores metadata about your data in Git (small files) while the actual data files are stored in remote storage (e.g., S3, GCS). This links specific data versions to specific code commits.


# Initialize DVC in your repo
dvc init

# Add your raw data to DVC
dvc add data/raw_transactions.csv

# Add your processed data to DVC
dvc add data/processed_features.parquet

# Push DVC-tracked files to remote storage (e.g., S3)
dvc remote add -d s3_remote s3://your-dvc-bucket/data
dvc push

# Commit .dvc files to Git
git add data/.dvcignore data/raw_transactions.csv.dvc data/processed_features.parquet.dvc
git commit -m "Add raw and processed data to DVC"
git push

Phase 3: CI/CD for ML – The Automated Pipeline

This is where MLOps truly shines, transforming manual, error-prone processes into automated, reliable workflows. We’ll discuss Continuous Integration (CI) for code and Continuous Delivery/Deployment (CD) for models.

Continuous Integration (CI)

CI ensures that every code change is automatically built, tested, and validated. For ML, this extends beyond traditional unit tests to include data validation and basic model sanity checks.

  • Linting & Static Analysis: Tools like Black, Flake8, Pylint ensure code style and catch potential issues.
  • Unit Tests: Verify individual functions (e.g., feature engineering steps, model prediction function).
  • Data Validation: Ensure incoming data conforms to expected schema, types, and ranges.
  • Dependency Checks: Confirm all required libraries are present and compatible.
  • Building Docker Images: Create fresh Docker images for training and inference services upon code changes.

Continuous Delivery/Deployment (CD)

CD automates the entire ML pipeline, from data preparation and model training to evaluation, registration, and deployment. It typically involves two main sub-pipelines:

1. Training Pipeline

This pipeline is triggered by new data, code changes, or on a schedule. Its goal is to produce a new, better model.

  • Trigger: New data lands in S3, new code is merged to main, or a nightly schedule.
  • Data Ingestion & Preprocessing: Fetch the latest data, apply cleaning, transformations, and feature engineering. Ensure data versioning is used (DVC).
  • Model Training: Train the model using the processed data. This could be distributed training for large datasets.
  • Model Evaluation: Evaluate the newly trained model against a holdout test set. Calculate performance metrics (accuracy, F1, etc.), and potentially perform bias and fairness checks.
  • Experiment Tracking: Log all run parameters, metrics, and artifacts to MLflow.
  • Model Versioning & Registration: If the model meets predefined performance thresholds and passes quality gates, it’s registered in the Model Registry (e.g., MLflow Model Registry) with a new version number and metadata. It might initially be registered as “Staging” or “Candidate.”
  • Artifact Storage: Store the trained model artifact (e.g., as a .pkl or TensorFlow SavedModel) and any associated data (e.g., scaler objects) in object storage (S3).
  • Approval Gates: Often, a human review or automated approval step is required before a model can proceed to production deployment.

Example: Simplified GitHub Actions Workflow for Training

This workflow would run whenever code changes in the main branch, potentially triggering a new training run.


# .github/workflows/train_model.yml
name: ML Model Training Pipeline

on:
  push:
    branches:
      - main
    paths:
      - 'src/**'
      - 'data/**' # If data changes trigger training, though DVC is better
      - 'requirements.txt'

jobs:
  train:
    runs-on: ubuntu-latest
    env:
      MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_TRACKING_URI }}
      MLFLOW_TRACKING_USERNAME: ${{ secrets.MLFLOW_TRACKING_USERNAME }}
      MLFLOW_TRACKING_PASSWORD: ${{ secrets.MLFLOW_TRACKING_PASSWORD }}
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      AWS_DEFAULT_REGION: 'us-east-1' # Or your region

    steps:
    - name: Checkout code
      uses: actions/checkout@v3
      with:
        fetch-depth: 0 # Needed for DVC

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.9'

    - name: Install dependencies
      run: pip install -r requirements.txt dvc[s3]

    - name: Pull DVC data
      run: dvc pull

    - name: Run MLflow training script
      run: |
        python src/train.py --alpha 0.5 --l1_ratio 0.5
        # The train.py script would handle:
        # - Loading data (potentially using DVC)
        # - Preprocessing
        # - Training model
        # - Evaluating model
        # - Logging metrics/params/model to MLflow
        # - Registering model in MLflow Model Registry (if criteria met)

    - name: Check model registration status (optional)
      # This step could use MLflow API to check if a new model version was registered
      # and its stage (e.g., 'staging' or 'candidate')
      run: echo "Training completed. Check MLflow UI for results."

<

Real-World MLOps Pipeline: From Notebook to Production
Generated Image

>

2. Deployment Pipeline

This pipeline takes an approved model and deploys it to an inference service.

  • Trigger: A new model version is approved for “Production” in the Model Registry, or a manual trigger.
  • Fetch Model: Retrieve the specific model version from the Model Registry.
  • Build Inference Image: Create a Docker image containing the model, the inference code (e.g., a FastAPI application), and all necessary dependencies.
  • Containerization: Push the Docker image to a container registry (e.g., Docker Hub, ECR, GCR).
  • Deployment to Staging: Deploy the new image to a staging environment. Run integration tests, end-to-end tests, and performance benchmarks.
  • Deployment to Production: If staging tests pass, deploy to production. This can involve strategies like:
    • Blue/Green Deployment: Deploy the new version (Green) alongside the old (Blue), then switch traffic.
    • Canary Deployment: Route a small percentage of traffic to the new version, monitor, and gradually increase traffic if stable.
    • A/B Testing: Route traffic to different model versions to compare business impact.
  • Post-Deployment Smoke Tests: Basic health checks and sanity tests to ensure the deployed service is functional.

Example: Simplified GitHub Actions Workflow for Deployment

This assumes a model is registered and marked for production in MLflow, and we’re deploying to a Kubernetes cluster.


# .github/workflows/deploy_model.yml
name: ML Model Deployment Pipeline

on:
  workflow_dispatch: # Manual trigger for now, can be automated by MLflow hooks
    inputs:
      model_name:
        description: 'MLflow Model Name to deploy'
        required: true
        default: 'FraudDetectionModel'
      model_version:
        description: 'MLflow Model Version to deploy'
        required: true
        default: '1' # Replace with actual version logic

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_TRACKING_URI }}
      MLFLOW_TRACKING_USERNAME: ${{ secrets.MLFLOW_TRACKING_USERNAME }}
      MLFLOW_TRACKING_PASSWORD: ${{ secrets.MLFLOW_TRACKING_PASSWORD }}
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      AWS_DEFAULT_REGION: 'us-east-1'
      ECR_REPOSITORY: 'fraud-detection-model'
      K8S_NAMESPACE: 'ml-production'

    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.9'

    - name: Install dependencies
      run: pip install -r requirements.txt mlflow

    - name: Login to AWS ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v2

    - name: Get model from MLflow Registry
      run: |
        # This script downloads the model artifact from MLflow
        # and prepares it for inclusion in the Docker image
        python -c "
import mlflow
logged_model = f'runs:/${{ github.event.inputs.model_name }}/${{ github.event.inputs.model_version }}/model' # Assuming model artifact is 'model' within run
mlflow.artifacts.download_artifacts(
    run_id=mlflow.get_run(logged_model.split('/')[1]).info.run_id,
    artifact_path='model',
    dst_path='model_artifact'
)
        "

    - name: Build and push Docker image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

    - name: Configure AWS credentials for K8s deployment
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_DEFAULT_REGION }}

    - name: Update Kubeconfig
      run: aws eks update-kubeconfig --name your-eks-cluster-name --region ${{ env.AWS_DEFAULT_REGION }}

    - name: Deploy to Kubernetes
      run: |
        # Replace image tag in your Kubernetes deployment manifest
        sed -i "s|YOUR_ECR_REGISTRY_PLACEHOLDER/$ECR_REPOSITORY:latest|$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG|g" k8s/deployment.yaml
        kubectl apply -f k8s/deployment.yaml -n ${{ env.K8S_NAMESPACE }}

    - name: Run smoke tests (optional)
      run: |
        # Ping the deployed service, send a sample request
        # curl -X POST -H "Content-Type: application/json" -d '{"features": [1,2,3]}' http://your-service-endpoint/predict
        echo "Deployment completed. Run smoke tests manually or via dedicated test job."

Phase 4: Production Operations, Monitoring, and Retraining

Deployment isn’t the end; it’s the beginning of the operational phase. Models in production require constant vigilance.

Inference Service

The deployed model provides predictions, typically via a RESTful API. FastAPI or Flask are common choices for building these services, often running in Docker containers orchestrated by Kubernetes.

Example: Simple FastAPI Inference Service


# src/inference_service.py
from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn
import mlflow
import os
import pandas as pd
import numpy as np
import joblib # For scaler

# Load model and scaler globally when the app starts
model = None
scaler = None

# Placeholder for model path (will be mounted in Docker)
MODEL_PATH = os.getenv("MODEL_PATH", "./model_artifact")
SCALER_PATH = os.getenv("SCALER_PATH", "./model_artifact/scaler.pkl") # Assuming scaler is saved

app = FastAPI(title="Fraud Detection ML API")

@app.on_event("startup")
async def load_model_and_scaler():
global model, scaler
try:
# Load MLflow model
model = mlflow.pyfunc.load_model(MODEL_PATH)
# Load scaler (if used during training)
scaler = joblib.load(SCALER_PATH)
print("ML model and scaler loaded successfully.")
except Exception as e:
print(f"Error loading model

Written by

Khader Vali

Senior Software Engineer specializing in cloud architecture, real-time systems, and enterprise-scale applications.

Share this article

Related Articles

Multimodal AI: Vision, Language & Audio Applications

Aug 01, 2026 · 19 min read

Build Production RAG Systems: LangChain & ChromaDB

Aug 26, 2026 · 18 min read

Build Production-Ready RAG with LangChain & ChromaDB

Sep 12, 2026 · 16 min read