Skip to content
devopsbymuh_
MLOps for DevOps Engineers · Part 11 of 11
11 min readby Muhammad Rashid

Build an End-to-End Production MLOps Platform

The finale: assemble the whole series — FastAPI, Docker, GitHub Actions, ECR, Terraform, EKS, MLflow, Prometheus/Grafana, and drift-triggered retraining — into one platform, and trace a change from git push to a promoted model serving traffic. Part 11 of the MLOps series.

Build an End-to-End Production MLOps Platform

Eleven parts ago, a data scientist dropped a Jupyter notebook in Slack, said "the model works, can you deploy it?", and walked away. We've spent the whole series answering that request properly. This finale doesn't introduce anything new — it steps back and shows how the ten pieces we built lock together into one platform, and then traces exactly what happens when a change enters it. If you've followed along, this is the article where it stops being ten tutorials and becomes one system.

The whole architecture on one page

Here's everything we built, and how a request and a model flow through it:

text
        git push
           │
   ┌───────▼────────┐
   │ GitHub Actions │  test ▸ build ▸ Trivy scan ▸ push        (part 4)
   └───────┬────────┘
           │ image:<sha>
      ┌────▼─────┐
      │   ECR    │  immutable tags                             (part 8)
      └────┬─────┘
           │        Terraform provisions it all: VPC/EKS/ECR/S3/IAM  (part 8)
   ┌───────▼─────────┐
   │   EKS cluster   │  Deployment ▸ Service ▸ Ingress ▸ HPA   (part 7)
   │  ┌───────────┐  │
   │  │ FastAPI   │  │  validated I/O, /health, /metrics     (parts 2,3,9)
   │  │ container │◀─┼── models:/house-price@production
   │  └─────┬─────┘  │                    ▲
   └────────┼────────┘                    │ promote (champion/challenger)
            │                    ┌────────┴────────┐
   scrape /metrics               │ MLflow Registry │            (part 5)
            │                    └────────▲────────┘
   ┌────────▼─────────┐                   │ retrain challenger
   │ Prometheus +     │          ┌────────┴────────┐
   │ Grafana + alerts │─────────▶│ drift detected  │           (parts 9,10)
   └──────────────────┘  signal  └─────────────────┘

Every box in that diagram was a part of this series. Nothing here's theoretical — it's the code in the repo you've been building. What makes it a platform, rather than a pile of tools, is that the boxes are connected to each other: the image CI builds is the image Kubernetes runs; the model MLflow promotes is the model the pods load; the metric the app emits is the metric that triggers the retrain.

The full platform: git push through GitHub Actions to ECR, EKS running FastAPI with the MLflow registry, Prometheus and Grafana, and drift-triggered retraining
The whole platform on one page — every box a part of this series, wired to the next.

The two loops that make it MLOps

Step back far enough and the whole platform is two feedback loops sharing infrastructure. Seeing them as two loops is the single most useful mental model in this series.

The deployment loop is the one you already knew from DevOps: code changes, tests run, an image is built and scanned, it ships to a cluster, and monitoring watches it. git push to running pod, automated end to end. If this were a normal web service, that loop would be the whole story.

The model loop is the one that makes it MLOps: data changes, drift is detected, a challenger model is trained and tracked, and it's promoted only if it beats the champion. There's no code change and no image rebuild: a rollout restart, or the next pod cycle, moves the pods onto the new model. This is the loop a plain web app doesn't have, and building it is why the second half of this series existed. The two loops meet in exactly one place: the running FastAPI service, which is both "the latest deployed code" and "the current production model." You deploy code through one loop; you promote a model through the other.

Two loops sharing the running service: the deployment loop (code to image to cluster) and the model loop (data to drift to retrain to promote)
Two loops meeting at the running service: deploy code through one, promote the model through the other.

Trace one change through the system

Concrete beats abstract, so let's follow a real change. Suppose retraining produces a better model. Here's every step it takes to reach a user, and which part of the series built each step:

  • The weekly drift job runs detect_drift.py, sees incomes have shifted, and exits non-zero (part 10).
  • That triggers a retrain: train_mlflow.py fits a challenger and registers it as house-price v4 in MLflow (part 5).
  • promote.py compares the challenger's MAE to the champion's. It's lower, so the production alias moves to v4 (part 5).
  • The pods are set up to load models:/house-price@production (part 5), so the next time a pod starts — a rollout, a scale-up, or a restart — it loads v4. No image rebuild, no code change (parts 5, 7).
  • Prometheus keeps scraping /metrics; the predicted-price histogram shifts to reflect the new model, and latency/error alerts stay quiet (part 9).
  • If v4 had been worse, promote.py would have rejected it, and nothing would have changed. The gate is the safety mechanism.

Now trace a code change instead: say you improve the batch endpoint. Push it; GitHub Actions runs the tests, builds an image tagged with the commit SHA, scans it with Trivy, and pushes it to ECR (part 4). You update the tag in the Deployment; Kubernetes rolls it out pod by pod, each new pod gated by the readiness probe so traffic only moves once it's actually serving (parts 3, 7). Two completely different kinds of change (a better model and better code) travel two separate, automated paths and meet in the same running service. That separation is the point.

What ties it together: everything is code

The quiet theme of all eleven parts is that nothing lives only in someone's head or someone's console. The model is a versioned artifact in a registry. The data is versioned in DVC, so every model can name the dataset it learned from. The API and its container are code. The pipeline is code. The Kubernetes state is code. The AWS infrastructure is code. The drift check and the retrain trigger are code. You could delete the entire AWS account, run terraform apply and the pipeline, and rebuild the whole platform from a git repo. That reproducibility — the same discipline we demanded of the model in part one, applied to every layer — is what separates a platform from a pile of scripts that happen to work.

What we deliberately left out

An honest finale admits its limits. This series is a spine, not the whole skeleton. We didn't build a feature store (worth it once several models share the same features) or model explainability (SHAP and friends, which matter the moment a prediction affects a person). We also skipped true production A/B testing and shadow deployments (testing a challenger on live traffic before promoting it), and GPU serving and batching (a different world, for deep learning). Each of these is a real part of mature MLOps. None of them changes the shape you now have — they slot into it. That's the payoff of building the spine first.

The whole series, in ten lines

  • 1 — MLOps is DevOps plus versioning, quality gates, and drift monitoring; training and inference are separate lifecycles.
  • 2 — FastAPI turns the model artifact into a validated, documented, real-time and batch service.
  • 3 — Docker packages the model with its exact runtime, so it runs the same everywhere.
  • 4 — GitHub Actions tests, builds, scans, and ships the image on every change — with no stored keys.
  • 5 — MLflow tracks every experiment and promotes models by champion/challenger, not by date.
  • 6 — DVC versions the data, so every model can name the exact dataset it learned from.
  • 7 — Kubernetes runs it all with self-healing, autoscaling, and zero-downtime, readiness-gated rollouts.
  • 8 — Terraform makes the VPC, cluster, registry, and IAM reproducible — and easy to destroy.
  • 9 — Prometheus and Grafana make the service and the model's behavior visible, with alerts.
  • 10 — Drift detection plus a champion/challenger retrain closes the feedback loop automatically.

If you started this series able to deploy a web app, and you ended it able to deploy, monitor, and automatically retrain a model in production, you didn't learn a new career — you extended the one you already have. That was the whole premise back in part one: you already knew 90% of this. The other 10% is everything we just built, and it's the part that turns "the model works on my laptop" into a system you can trust at 3am. The complete platform is in the series repo. Thanks for building it with me — now go put a model in production.

$ ./work-with-me.sh

Want this in your job, not just your notes?

I take engineers from wherever they are to hired-in-6-months — real projects, code reviews, and mock interviews. Or if you just need a hand shipping something to production, let's work together.

Book a 1:1 call

or subscribe on YouTube — free, forever.

$ subscribe --new-articles

Get new articles in your inbox

One email when a new hands-on guide goes live — Kubernetes, AWS, CI/CD, MLOps. No spam, unsubscribe anytime.