
Mlops Deployment
- 16 installs
- 4 repo stars
- Updated January 5, 2026
- pluginagentmarketplace/custom-plugin-ai-data-scientist
Helps with devops & ci/cd tasks.
About
mlops-deployment is a Claude Code skill for devops & ci/cd. It helps solo builders move faster with AI-assisted development.
- mlops-deployment
- DevOps & CI/CD
- AI-coding skill
Mlops Deployment by the numbers
- 16 all-time installs (skills.sh)
- Ranked #943 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/pluginagentmarketplace/custom-plugin-ai-data-scientist --skill mlops-deploymentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 16 |
|---|---|
| repo stars | ★ 4 |
| Last updated | January 5, 2026 |
| Repository | pluginagentmarketplace/custom-plugin-ai-data-scientist ↗ |
What it does
Helps with devops & ci/cd tasks.
Files
MLOps & Deployment
Deploy and maintain ML models in production with robust infrastructure.
Quick Start
Dockerize ML Model
FROM python:3.10-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy model and code
COPY model.pkl .
COPY app.py .
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8000/health || exit 1
# Run
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]FastAPI Model Serving
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import joblib
import numpy as np
app = FastAPI()
model = joblib.load('model.pkl')
class PredictionRequest(BaseModel):
features: list[float]
class PredictionResponse(BaseModel):
prediction: float
probability: float
@app.post('/predict', response_model=PredictionResponse)
async def predict(request: PredictionRequest):
try:
features = np.array(request.features).reshape(1, -1)
prediction = model.predict(features)[0]
probability = model.predict_proba(features)[0].max()
return {
'prediction': float(prediction),
'probability': float(probability)
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get('/health')
async def health():
return {'status': 'healthy'}Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: ml-model
spec:
replicas: 3
selector:
matchLabels:
app: ml-model
template:
metadata:
labels:
app: ml-model
spec:
containers:
- name: ml-model
image: myregistry/ml-model:v1.0.0
ports:
- containerPort: 8000
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: ml-model-service
spec:
selector:
app: ml-model
ports:
- protocol: TCP
port: 80
targetPort: 8000
type: LoadBalancerCI/CD Pipeline (GitHub Actions)
name: ML Pipeline
on:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.10
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: |
pytest tests/ --cov=src
train:
needs: test
runs-on: ubuntu-latest
steps:
- name: Train model
run: python src/train.py
- name: Evaluate model
run: python src/evaluate.py
deploy:
needs: train
runs-on: ubuntu-latest
steps:
- name: Build Docker image
run: |
docker build -t ${{ secrets.REGISTRY }}/ml-model:${{ github.sha }} .
- name: Push to registry
run: |
docker push ${{ secrets.REGISTRY }}/ml-model:${{ github.sha }}
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/ml-model \
ml-model=${{ secrets.REGISTRY }}/ml-model:${{ github.sha }}Model Monitoring
from prometheus_client import Counter, Histogram, start_http_server
import time
# Metrics
prediction_counter = Counter(
'model_predictions_total',
'Total predictions'
)
prediction_latency = Histogram(
'model_prediction_latency_seconds',
'Prediction latency'
)
@app.post('/predict')
async def predict(request: PredictionRequest):
start_time = time.time()
try:
prediction = model.predict(request.features)
prediction_counter.inc()
finally:
latency = time.time() - start_time
prediction_latency.observe(latency)
return {'prediction': prediction}
# Start metrics server
start_http_server(9090)Data Drift Detection
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset
# Reference data (training)
reference = pd.read_csv('training_data.csv')
# Current production data
current = pd.read_csv('production_data.csv')
# Generate drift report
report = Report(metrics=[DataDriftPreset()])
report.run(reference_data=reference, current_data=current)
# Check drift
drift_detected = report.as_dict()['metrics'][0]['result']['dataset_drift']
if drift_detected:
print("WARNING: Data drift detected!")
trigger_retraining()MLflow Model Registry
import mlflow
import mlflow.sklearn
mlflow.set_tracking_uri("http://localhost:5000")
with mlflow.start_run():
# Train model
model = RandomForestClassifier()
model.fit(X_train, y_train)
# Log parameters
mlflow.log_param("n_estimators", 100)
# Log metrics
accuracy = model.score(X_test, y_test)
mlflow.log_metric("accuracy", accuracy)
# Log model
mlflow.sklearn.log_model(
model,
"model",
registered_model_name="RandomForest"
)
# Promote to production
client = mlflow.tracking.MlflowClient()
client.transition_model_version_stage(
name="RandomForest",
version=1,
stage="Production"
)A/B Testing
@app.route('/predict', methods=['POST'])
def predict():
user_id = request.json['user_id']
features = request.json['features']
# 10% traffic to model B
if hash(user_id) % 100 < 10:
model = model_b
model_version = 'B'
else:
model = model_a
model_version = 'A'
prediction = model.predict([features])[0]
# Log for analysis
log_prediction(user_id, model_version, prediction)
return {
'prediction': prediction,
'model_version': model_version
}Cloud Deployment
AWS SageMaker
import sagemaker
from sagemaker.sklearn import SKLearn
estimator = SKLearn(
entry_point='train.py',
framework_version='1.0-1',
instance_type='ml.m5.xlarge',
role=sagemaker_role
)
estimator.fit({'training': 's3://bucket/data/train'})
# Deploy
predictor = estimator.deploy(
initial_instance_count=2,
instance_type='ml.m5.large'
)Google Cloud Vertex AI
from google.cloud import aiplatform
aiplatform.init(project='my-project', location='us-central1')
model = aiplatform.Model.upload(
display_name='sklearn-model',
artifact_uri='gs://bucket/model',
serving_container_image_uri='us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-0:latest'
)
endpoint = model.deploy(
machine_type='n1-standard-2',
min_replica_count=1,
max_replica_count=3
)Best Practices
1. Version everything: Code, data, models 2. Monitor continuously: Performance, drift, errors 3. Automate testing: Unit, integration, performance 4. Use feature flags: Gradual rollouts 5. Implement rollback: Quick recovery from issues 6. Scale horizontally: Multiple replicas 7. Log predictions: For debugging and retraining
# MLOps Docker Configuration
# Production ML model deployment settings
# Service Configuration
service:
name: "ml-model-service"
version: "1.0.0"
description: "Production ML model serving"
# Docker Configuration
docker:
base_image: "python:3.10-slim"
registry: "${DOCKER_REGISTRY}"
tag_format: "${SERVICE_NAME}:${VERSION}-${GIT_SHA}"
# Multi-stage build
stages:
builder:
install_deps: true
compile_models: true
runtime:
user: "appuser"
workdir: "/app"
# Resource limits
resources:
memory: "2g"
cpus: "2.0"
# Health check
healthcheck:
endpoint: "/health"
interval: "30s"
timeout: "3s"
retries: 3
# Model Serving
serving:
framework: "fastapi" # fastapi, flask, triton, seldon
port: 8000
workers: 4
timeout: 60
# Endpoints
endpoints:
predict: "/predict"
batch_predict: "/batch_predict"
health: "/health"
metrics: "/metrics"
# Request validation
validation:
enabled: true
max_batch_size: 100
max_request_size_mb: 10
# Model Loading
model:
format: "pickle" # pickle, onnx, joblib, pytorch, tensorflow
path: "/app/models/model.pkl"
version_path: "/app/models/version.txt"
# Hot reload
hot_reload:
enabled: true
check_interval: 60
# Caching
cache:
enabled: true
max_size_mb: 500
# Monitoring
monitoring:
prometheus:
enabled: true
port: 9090
path: "/metrics"
metrics:
- "prediction_latency"
- "prediction_count"
- "error_rate"
- "model_version"
- "input_distribution"
logging:
level: "INFO"
format: "json"
include_request_id: true
# Scaling
scaling:
min_replicas: 2
max_replicas: 10
autoscaling:
enabled: true
target_cpu_percent: 70
target_memory_percent: 80
scale_up_cooldown: 60
scale_down_cooldown: 300
# Security
security:
# API authentication
auth:
enabled: true
type: "api_key" # api_key, jwt, oauth2
# Rate limiting
rate_limit:
enabled: true
requests_per_minute: 100
burst: 20
# Input sanitization
input_validation:
max_string_length: 10000
allowed_types: ["int", "float", "string", "list"]
# CI/CD Integration
cicd:
trigger_on:
- "model_update"
- "code_change"
stages:
- "lint"
- "test"
- "build"
- "push"
- "deploy"
rollback:
enabled: true
on_error_rate: 0.05
on_latency_p99: 1000
# Environment Variables
environment:
required:
- "MODEL_PATH"
- "LOG_LEVEL"
optional:
- "MLFLOW_TRACKING_URI"
- "PROMETHEUS_MULTIPROC_DIR"
MLOps Deployment Guide
Deployment Strategy Selection
Model Complexity / Traffic Volume
│
▼
┌────────────────────────────────────────────────────────┐
│ Low Traffic │
│ ┌─────────┐ ┌─────────┐ │
│ │ Simple │ ──► Flask/FastAPI │ Complex │──► Docker │
│ │ Model │ on VM │ Model │ on VM │
│ └─────────┘ └─────────┘ │
├────────────────────────────────────────────────────────┤
│ High Traffic │
│ ┌─────────┐ ┌─────────┐ │
│ │ Simple │ ──► Kubernetes │ Complex │──► K8s + │
│ │ Model │ with HPA │ Model │ Triton │
│ └─────────┘ └─────────┘ │
└────────────────────────────────────────────────────────┘Deployment Options Comparison
| Option | Complexity | Scalability | Cost | Best For |
|---|---|---|---|---|
| Flask on VM | Low | Low | Low | Prototypes |
| FastAPI + Docker | Medium | Medium | Medium | Small prod |
| Kubernetes | High | High | High | Production |
| AWS SageMaker | Medium | High | Variable | AWS users |
| GCP Vertex AI | Medium | High | Variable | GCP users |
| Triton Server | High | Very High | High | GPU inference |
| TensorFlow Serving | Medium | High | Medium | TF models |
Docker Best Practices
# Multi-stage build for smaller images
FROM python:3.10-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip wheel --no-cache-dir -r requirements.txt -w /wheels
FROM python:3.10-slim
# Security: run as non-root
RUN useradd -m appuser
USER appuser
WORKDIR /app
# Copy wheels and install
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/*
# Copy application
COPY --chown=appuser:appuser . .
# Health check
HEALTHCHECK --interval=30s CMD curl -f http://localhost:8000/health
# Run
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]Kubernetes Checklist
Pre-deployment:
□ Docker image built and pushed
□ Resource limits defined
□ Health checks configured
□ ConfigMaps/Secrets created
□ Service account configured
Deployment:
□ Deployment manifest applied
□ Service (LoadBalancer/ClusterIP) created
□ HPA configured
□ PodDisruptionBudget set
□ Network policies defined
Post-deployment:
□ Health endpoints verified
□ Metrics being collected
□ Logs flowing to aggregator
□ Alerts configured
□ Rollback procedure testedMonitoring Metrics
| Metric | Type | Alert Threshold |
|---|---|---|
| Latency P50 | Histogram | > 100ms |
| Latency P99 | Histogram | > 500ms |
| Error Rate | Counter | > 1% |
| Request Rate | Counter | Anomaly |
| CPU Usage | Gauge | > 80% |
| Memory Usage | Gauge | > 85% |
| Model Staleness | Gauge | > 24h |
CI/CD Pipeline Stages
1. Lint & Test
└── Code quality checks
└── Unit tests
└── Model validation tests
2. Build
└── Docker image build
└── Security scan
└── Push to registry
3. Deploy Staging
└── Deploy to staging
└── Integration tests
└── Performance tests
4. Deploy Production
└── Canary deployment (10%)
└── Monitor metrics
└── Full rollout
└── Smoke testsRollback Strategy
# Automatic rollback triggers
rollback:
triggers:
- error_rate > 5%
- latency_p99 > 2s
- health_check_failures > 3
procedure:
1. Detect issue (monitoring)
2. Stop traffic to new version
3. Scale up old version
4. Redirect traffic
5. Scale down new version
6. Post-mortem analysisSecurity Checklist
□ API authentication (API key, JWT, OAuth)
□ Rate limiting configured
□ Input validation/sanitization
□ HTTPS/TLS enabled
□ Secrets in secure vault
□ Container security scan
□ Network policies
□ Audit logging enabled
□ RBAC configured#!/usr/bin/env python3
"""
FastAPI Model Serving Server
Production-ready ML model deployment
"""
from fastapi import FastAPI, HTTPException, Request, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
import numpy as np
import joblib
import time
import logging
from typing import List, Optional, Dict, Any
from contextlib import asynccontextmanager
import os
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Request/Response Models
class PredictionRequest(BaseModel):
features: List[float] = Field(..., description="Input features")
request_id: Optional[str] = Field(None, description="Optional request ID")
class Config:
json_schema_extra = {
"example": {
"features": [1.0, 2.0, 3.0, 4.0],
"request_id": "req-001"
}
}
class BatchPredictionRequest(BaseModel):
instances: List[List[float]] = Field(..., description="Batch of instances")
request_id: Optional[str] = None
class PredictionResponse(BaseModel):
prediction: float
probability: Optional[float] = None
request_id: Optional[str] = None
latency_ms: float
model_version: str
class BatchPredictionResponse(BaseModel):
predictions: List[float]
probabilities: Optional[List[float]] = None
request_id: Optional[str] = None
latency_ms: float
model_version: str
class HealthResponse(BaseModel):
status: str
model_loaded: bool
model_version: str
uptime_seconds: float
class MetricsResponse(BaseModel):
total_predictions: int
avg_latency_ms: float
error_rate: float
model_version: str
# Global state
class ModelState:
def __init__(self):
self.model = None
self.model_version = "unknown"
self.start_time = time.time()
self.prediction_count = 0
self.error_count = 0
self.total_latency = 0.0
state = ModelState()
def load_model(model_path: str = "model.pkl"):
"""Load model from file."""
try:
state.model = joblib.load(model_path)
state.model_version = os.environ.get("MODEL_VERSION", "1.0.0")
logger.info(f"Model loaded: version {state.model_version}")
return True
except Exception as e:
logger.error(f"Failed to load model: {e}")
return False
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Lifecycle manager for startup/shutdown."""
# Startup
model_path = os.environ.get("MODEL_PATH", "model.pkl")
if os.path.exists(model_path):
load_model(model_path)
else:
logger.warning(f"Model file not found: {model_path}")
# Create a dummy model for demo
from sklearn.ensemble import RandomForestClassifier
state.model = RandomForestClassifier()
state.model.fit([[0, 0], [1, 1]], [0, 1])
state.model_version = "demo-1.0.0"
logger.info("Using demo model")
yield
# Shutdown
logger.info("Shutting down model server")
# Create FastAPI app
app = FastAPI(
title="ML Model Server",
description="Production-ready ML model serving API",
version="1.0.0",
lifespan=lifespan
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.post("/predict", response_model=PredictionResponse)
async def predict(request: PredictionRequest):
"""Make a single prediction."""
start_time = time.time()
if state.model is None:
raise HTTPException(status_code=503, detail="Model not loaded")
try:
features = np.array(request.features).reshape(1, -1)
prediction = float(state.model.predict(features)[0])
# Get probability if available
probability = None
if hasattr(state.model, 'predict_proba'):
proba = state.model.predict_proba(features)[0]
probability = float(max(proba))
latency = (time.time() - start_time) * 1000
# Update metrics
state.prediction_count += 1
state.total_latency += latency
return PredictionResponse(
prediction=prediction,
probability=probability,
request_id=request.request_id,
latency_ms=round(latency, 2),
model_version=state.model_version
)
except Exception as e:
state.error_count += 1
logger.error(f"Prediction error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/batch_predict", response_model=BatchPredictionResponse)
async def batch_predict(request: BatchPredictionRequest):
"""Make batch predictions."""
start_time = time.time()
if state.model is None:
raise HTTPException(status_code=503, detail="Model not loaded")
try:
features = np.array(request.instances)
predictions = state.model.predict(features).tolist()
probabilities = None
if hasattr(state.model, 'predict_proba'):
proba = state.model.predict_proba(features)
probabilities = [float(max(p)) for p in proba]
latency = (time.time() - start_time) * 1000
state.prediction_count += len(predictions)
state.total_latency += latency
return BatchPredictionResponse(
predictions=[float(p) for p in predictions],
probabilities=probabilities,
request_id=request.request_id,
latency_ms=round(latency, 2),
model_version=state.model_version
)
except Exception as e:
state.error_count += 1
logger.error(f"Batch prediction error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health", response_model=HealthResponse)
async def health():
"""Health check endpoint."""
return HealthResponse(
status="healthy" if state.model is not None else "unhealthy",
model_loaded=state.model is not None,
model_version=state.model_version,
uptime_seconds=round(time.time() - state.start_time, 2)
)
@app.get("/metrics", response_model=MetricsResponse)
async def metrics():
"""Get server metrics."""
total = state.prediction_count + state.error_count
error_rate = state.error_count / total if total > 0 else 0.0
avg_latency = state.total_latency / state.prediction_count if state.prediction_count > 0 else 0.0
return MetricsResponse(
total_predictions=state.prediction_count,
avg_latency_ms=round(avg_latency, 2),
error_rate=round(error_rate, 4),
model_version=state.model_version
)
@app.post("/reload")
async def reload_model(background_tasks: BackgroundTasks):
"""Reload model from disk."""
model_path = os.environ.get("MODEL_PATH", "model.pkl")
background_tasks.add_task(load_model, model_path)
return {"status": "reload_initiated"}
def main():
"""Run the server."""
import uvicorn
uvicorn.run(
"model_server:app",
host="0.0.0.0",
port=8000,
reload=False,
workers=4
)
if __name__ == "__main__":
main()