MLOps on a Shoestring: A Complete Stack for Under $200/Month
Machine Learning·By Islam Gamal··30 min read

MLOps on a Shoestring: A Complete Stack for Under $200/Month

The pragmatic MLOps stack for solo builders and small teams — model registry, experiment tracking, feature store, drift monitoring, and CI/CD — all under $200/month with open-source and free tiers.

Islam Gamal
Islam Gamal
AI · Data · Automation Engineer
TL;DR

Enterprise MLOps stacks cost $10k+/month and require a platform team. For solo builders and small startups, you can assemble a complete MLOps stack — experiment tracking, model registry, feature store, batch and online inference, drift monitoring, CI/CD — for under $200/month using MLflow, DVC, Feast, Evidently, and one small cloud VM. This article is the exact stack, exact configs, and the trade-offs you accept in exchange for the savings.

Key takeaways
  • MLflow + DVC + Postgres covers experiment tracking + model registry + versioning.
  • Feast in local mode + Postgres = free feature store good enough for <1M rows.
  • Evidently + a cron job replaces $2k/mo drift monitoring platforms.
  • GitHub Actions runs CI/CD training + eval pipelines for free.
  • One $40/month VM hosts MLflow, Postgres, and a small model serving layer.
  • Total spend: $180/month all-in for a stack that carries you to Series A.

The three MLOps stacks — pick the right one

StackCost/moFor
Shoestring (this article)$180Solo builders, seed-stage startups, side projects
Mid (SageMaker/Vertex AI managed pieces)$2k–$8kSeries A/B teams, one ML engineer
Enterprise (Databricks, dedicated platform)$20k+Series C+, dedicated ML platform team

The shoestring stack is not 'toy'. It ran the first two years of several profitable ML products. It graduates cleanly to the mid stack when justified by team size or scale.

The complete stack

ConcernToolRuns where
Experiment trackingMLflowOne VM
Model registryMLflowSame VM
Data + model versioningDVC + S3-compatible (Backblaze B2)Local + B2
Feature storeFeast (local mode)Same VM + Postgres
Feature DBPostgres (Cloud SQL / Neon)Managed
Training computeGitHub Actions (2-core) or one spot VMGHA
Batch inferenceCron on the VMSame VM
Online inferenceFastAPI on Cloud RunCloud Run
Drift monitoringEvidently + cron + Slack webhookSame VM
DashboardingMetabase or GrafanaSame VM

The $40 VM that runs everything

Rent a 4-core, 8GB VM from Hetzner, DigitalOcean, or OVH. Install Docker + docker-compose. This one machine hosts MLflow, Postgres (for MLflow), Metabase, and cron jobs.

# docker-compose.yml
services:
  mlflow:
    image: ghcr.io/mlflow/mlflow:v2.16.0
    command: mlflow server --host 0.0.0.0 --backend-store-uri postgresql://mlflow:...@db/mlflow --default-artifact-root s3://ml-artifacts/
    environment:
      AWS_ACCESS_KEY_ID: ${B2_KEY_ID}
      AWS_SECRET_ACCESS_KEY: ${B2_APP_KEY}
      MLFLOW_S3_ENDPOINT_URL: https://s3.eu-central-003.backblazeb2.com
    ports: ["5000:5000"]
  db:
    image: postgres:16
    volumes: [pgdata:/var/lib/postgresql/data]
  metabase:
    image: metabase/metabase:v0.50
    ports: ["3000:3000"]
volumes: { pgdata: {} }

Reverse proxy via Caddy for HTTPS + basic auth. Total setup: 90 minutes.

MLflow: experiment tracking + registry in one

import mlflow
mlflow.set_tracking_uri("https://mlflow.example.com")
mlflow.set_experiment("churn-v3")

with mlflow.start_run():
    mlflow.log_params({"lr": 0.01, "depth": 6})
    mlflow.log_metrics({"auc": 0.87, "f1": 0.72})
    mlflow.sklearn.log_model(model, "model", registered_model_name="churn")

# Promote to staging/production via UI or:
mlflow.register_model("runs:/<run_id>/model", "churn")
client.transition_model_version_stage("churn", 3, "Production")

Serve the production model:

model = mlflow.pyfunc.load_model("models:/churn/Production")
prediction = model.predict(features_df)

DVC for data + model versioning

DVC tracks large files in Git without bloating the repo. It stores actual bytes in your S3-compatible bucket.

dvc init
dvc remote add -d origin s3://ml-artifacts/dvc \
    --endpointurl https://s3.eu-central-003.backblazeb2.com

dvc add data/raw/customers.parquet   # creates .dvc file
git add data/raw/customers.parquet.dvc
git commit -m "add raw customers snapshot"
dvc push

Every commit now pins the exact data + model bytes used. Reproducibility for free.

Feast: feature store in local mode

Feast's local registry writes to Postgres. Online store lives in Postgres too. Enough for <1M entities and sub-100ms serving.

# feature_repo/customer_features.py
from feast import Entity, FeatureView, Field, ValueType
from feast.types import Float32, Int64

customer = Entity(name="customer_id", value_type=ValueType.INT64)

customer_stats_fv = FeatureView(
    name="customer_stats",
    entities=[customer],
    schema=[Field(name="ltv_90d", dtype=Float32),
            Field(name="orders_90d", dtype=Int64)],
    source=customer_stats_source,
)

Materialize nightly via cron:

feast materialize-incremental $(date -u +%FT%TZ)

GitHub Actions as a training runner

For models that train in <6h on 2 cores + 16GB, GitHub Actions is free enough (2000 min/mo on free tier, 50k on Pro).

name: train
on: { workflow_dispatch: {}, schedule: [{ cron: "0 3 * * *" }] }
jobs:
  train:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -r requirements.txt
      - run: dvc pull
      - run: python train.py
        env:
          MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_URI }}
          MLFLOW_TRACKING_USERNAME: ${{ secrets.MLFLOW_USER }}
          MLFLOW_TRACKING_PASSWORD: ${{ secrets.MLFLOW_PASS }}

Heavier training? Spin up a spot GPU VM on Lambda Labs (~$0.60/h) triggered by GH Actions — pay only for training minutes.

Evidently for drift, no SaaS needed

from evidently import ColumnMapping
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset, TargetDriftPreset

report = Report(metrics=[DataDriftPreset(), TargetDriftPreset()])
report.run(reference_data=ref_df, current_data=today_df)
report.save_html("reports/drift-{today}.html")

if report.as_dict()["metrics"][0]["result"]["dataset_drift"]:
    post_to_slack("Drift detected in churn features")

Cron this nightly; publish HTML reports to S3. Total incremental cost: $0.

Online inference via Cloud Run

FastAPI wrapper around mlflow.pyfunc.load_model, containerized, deployed to Cloud Run — the pattern in Cloud Run + Cloud SQL blueprint.

Pro tip: Cache the loaded model in a module-level variable; Cloud Run reuses warm instances. Cold start = model reload = a few seconds.

Cost breakdown

ComponentMonthly cost
VM (Hetzner 4C/8GB)$8
Postgres for app/feature store (Neon free → paid)$25
Backblaze B2 (500GB)$3
Cloud Run inference$10–50
Domain + Caddy TLS$1
Slack (free)$0
Occasional GPU spot VM (10h/mo)$6
Total~$70–100/mo

Even doubling for redundancy, you're under $200 — vs $2k+ for the managed equivalent.

What you give up (and when to graduate)

  • One VM = one bus factor. Automate backups; run monthly restore drills — see self-hosting n8n on Kubernetes for backup discipline.
  • No auto-scaling training — beyond ~4h training runs, move to a managed platform.
  • You are the SRE. Budget 2h/month for maintenance.
  • Graduate when: >2 ML engineers, >10 models in production, or when compliance requires SOC 2 with a managed control plane.

Learning path

If you're starting from zero, sequence:

  1. AI + data engineer roadmap for foundations.
  2. evaluating LLMs and classical ML for eval discipline.
  3. observability for AI systems once you have models in prod.
  4. BigQuery cost control when analytics spend starts appearing.
#MLOps#ML#Monitoring#Open Source#Cost

Building something similar?

I help teams ship production-grade AI agents, n8n workflows, and data platforms. Let's talk about what you're building.

Work with me
Islam Gamal
Written by

Islam Gamal

AI, Data & Automation Engineer. I design and ship production AI agents, n8n workflows, and cloud data platforms — with a focus on reliability, cost, and measurable business impact. Founder of Tashghil and Tek bil Arabi.

More from Islam Gamal

Browse every article by Islam Gamal on the author page.