Deploying ML Models on AWS, Azure and GCP
AWS, Azure and Google Cloud all host models behind managed endpoints, but each expects a different serving contract. This guide runs one real model behind all three contracts locally, including Microsoft's own inference server, and shows what changes between clouds and what should stay exactly the same.
GloryTecks Team
Training & Placement Faculty
Last updated: 8 October 2026
Every major cloud will host a trained model behind a managed endpoint: Amazon SageMaker AI on AWS, Azure Machine Learning on Azure, and Gemini Enterprise Agent Platform on Google Cloud, which now holds what was Vertex AI: Vertex AI documentation links redirect to it. AWS renamed its service on 3 December 2024; Amazon SageMaker AI is what used to be Amazon SageMaker, and its API names did not change (AWS: What is SageMaker AI).
The steps to deploy ML models look alike on all three: package the model, wrap it in code the platform knows how to call, create an endpoint and send traffic. The part that really differs is that wrapper, the serving contract. This guide runs one model behind all three contracts on a laptop and compares them.
The managed options
| AWS | Azure | Google Cloud | |
|---|---|---|---|
| Service | Amazon SageMaker AI | Azure Machine Learning | Gemini Enterprise Agent Platform |
| Real-time serving | Real-time and serverless endpoints | Managed online endpoints | Online inference endpoints |
| Your own code | Custom container | Scoring script or custom container | Custom container |
| Gradual rollout | Production and shadow variants | Several deployments behind one endpoint | Traffic split across models on one endpoint |
Azure recommends managed online endpoints for real-time serving, and keeps endpoints separate from deployments so you can roll out safely, compare champion and challenger, and mirror traffic (Microsoft Learn: online endpoints). Google lets a new model join an endpoint with a small share of traffic and grow to 100% (Google Cloud: deploy to an endpoint).
Three contracts
- SageMaker AI: a web server on port 8080 that answers GET /ping with 200 and POST /invocations with predictions. Health checks must pass within 8 minutes of startup, each request must finish within 60 seconds, and the model files appear read-only in /opt/ml/model (AWS: inference containers).
- Google Cloud: the platform passes the port, health route, prediction route and model location in environment variables (AIP_HTTP_PORT, AIP_HEALTH_ROUTE, AIP_PREDICT_ROUTE, AIP_STORAGE_URI). Requests carry an instances array and responses a predictions array, each up to 1.5 MB (Google Cloud: custom containers).
- Azure Machine Learning: a scoring script with an init function, called once, and a run function, called per request, with the model folder in AZUREML_MODEL_DIR (Microsoft Learn: online endpoints). Microsoft publishes the same inference server as a Python package, azmlinfsrv, for running the script locally (Microsoft Learn: inference server).
One model, run behind all three
The model predicts concrete compressive strength from a mix's ingredients and age, trained on the UCI dataset of 1,030 mixes, under CC BY 4.0 (UCI: Concrete Compressive Strength). One FastAPI app meets both container contracts:
import os
import joblib
import pandas as pd
from fastapi import FastAPI, HTTPException, Request, Response
# SageMaker copies the model to /opt/ml/model; Google passes its location in AIP_STORAGE_URI. MODEL_DIR overrides both locally.
MODEL_DIR = os.environ.get("MODEL_DIR", "/opt/ml/model")
model = joblib.load(os.path.join(MODEL_DIR, "model.joblib"))
FEATURES = list(model.feature_names_in_)
app = FastAPI()
def predict(body):
if not isinstance(body, dict) or not isinstance(body.get("instances"), list): # a client error, not a server fault
raise HTTPException(status_code=400, detail='expected a JSON object with an "instances" list')
return [round(float(v), 2) for v in model.predict(pd.DataFrame(body["instances"], columns=FEATURES))]
# Amazon SageMaker AI contract: port 8080, GET /ping, POST /invocations
@app.get("/ping")
def ping():
return Response(status_code=200)
@app.post("/invocations")
async def invocations(request: Request):
body = await request.json()
return {"predictions": predict(body)}
# Google Cloud (Gemini Enterprise Agent Platform) contract: the platform sets the routes and the port
@app.get(os.environ.get("AIP_HEALTH_ROUTE", "/health"))
def health():
return {"status": "ok"}
@app.post(os.environ.get("AIP_PREDICT_ROUTE", "/predict"))
async def vertex_predict(request: Request):
body = await request.json()
return {"predictions": predict(body)}The Azure scoring script:
import json
import os
import joblib
import pandas as pd
model = None
def init(): # Azure Machine Learning calls this once when the deployment starts
global model
model_dir = os.environ.get("AZUREML_MODEL_DIR", "model")
model = joblib.load(os.path.join(model_dir, "model.joblib"))
def run(raw_data): # and this for every request to /score
rows = json.loads(raw_data)["data"]
frame = pd.DataFrame(rows, columns=list(model.feature_names_in_))
return {"predictions": [round(float(v), 2) for v in model.predict(frame)]}A script starts both servers, sends the same three mixes in each platform's format, then sends requests in the wrong shape:
import os
import shutil
import subprocess
import sys
import time
import joblib
import pandas as pd
import requests
from sklearn.ensemble import HistGradientBoostingRegressor
mix = pd.read_csv("concrete.csv") # UCI Concrete Compressive Strength (CC BY 4.0)
features = [c for c in mix.columns if c != "strength_mpa"]
os.makedirs("model", exist_ok=True)
joblib.dump(HistGradientBoostingRegressor(random_state=0).fit(mix[features], mix.strength_mpa), "model/model.joblib")
rows = mix[features].head(3).values.tolist()
servers = [
subprocess.Popen([sys.executable, "-m", "uvicorn", "app:app", "--port", "8080", "--log-level", "warning"],
env=dict(os.environ, MODEL_DIR="model", AIP_HEALTH_ROUTE="/health", AIP_PREDICT_ROUTE="/predict")),
subprocess.Popen([shutil.which("azmlinfsrv"), "--entry_script", "score.py", "--port", "5005"], # Azure's own server
env=dict(os.environ, AZUREML_MODEL_DIR="model"), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL),
]
def wait_for(url):
for _ in range(120):
try:
if requests.get(url, timeout=1).ok:
return
except requests.ConnectionError:
pass
time.sleep(0.5)
raise RuntimeError(f"{url} did not start")
try:
for url in ("http://127.0.0.1:8080/ping", "http://127.0.0.1:8080/health", "http://127.0.0.1:5005/"):
wait_for(url)
calls = {"SageMaker AI POST /invocations": ("http://127.0.0.1:8080/invocations", {"instances": rows}),
"Google Cloud POST /predict": ("http://127.0.0.1:8080/predict", {"instances": rows}),
"Azure ML POST /score": ("http://127.0.0.1:5005/score", {"data": rows})}
answers = {}
for name, (url, body) in calls.items():
reply = requests.post(url, json=body, timeout=30)
answers[name] = reply.json()["predictions"]
print(f"{name:<34} {reply.status_code} {answers[name]}")
print(f"actual strengths (MPa): {mix.strength_mpa.head(3).round(2).tolist()}")
print(f"same predictions from all three contracts: {len({tuple(a) for a in answers.values()}) == 1}")
print("\nrequests in the wrong shape:")
for name, url, body in (("SageMaker AI, 'data' instead of 'instances'", "http://127.0.0.1:8080/invocations", {"data": rows}),
("Azure ML, 'instances' instead of 'data'", "http://127.0.0.1:5005/score", {"instances": rows})):
print(f" {name:<46} HTTP {requests.post(url, json=body, timeout=30).status_code}")
finally:
for server in servers:
if os.name == "nt":
subprocess.run(["taskkill", "/T", "/F", "/PID", str(server.pid)], capture_output=True)
else:
server.terminate()SageMaker AI POST /invocations 200 [71.13, 68.29, 41.1]
Google Cloud POST /predict 200 [71.13, 68.29, 41.1]
Azure ML POST /score 200 [71.13, 68.29, 41.1]
actual strengths (MPa): [79.99, 61.89, 40.27]
same predictions from all three contracts: True
requests in the wrong shape:
SageMaker AI, 'data' instead of 'instances' HTTP 400
Azure ML, 'instances' instead of 'data' HTTP 500What the output shows
- The model never changed. All three contracts returned the same predictions, 71.13, 68.29 and 41.10 MPa, for mixes measured at 79.99, 61.89 and 40.27. Everything cloud-specific lived in a thin wrapper.
- One image can serve two clouds. Google lets the platform choose the routes, so the container built for SageMaker's fixed routes also met Google's contract.
- Bad requests behaved differently. The container checked its input and answered 400, a client error. Azure's server turned the scoring script's exception into a 500, which looks like a server fault to callers and to alerting, so a scoring script should check its own input.
- Most deployment failures can be caught on a laptop. A wrong route, body shape or model path fails locally exactly as it would in the cloud, minutes earlier and for free.
For writing the service itself, see serving ML models with FastAPI and Docker. For rolling a new version out safely, see blue-green deployments and shadow deployment and canary releases. For the platforms beyond serving, see Vertex AI and SageMaker compared, and for keeping the bill down, see cost optimisation for ML workloads.
Choosing where to deploy
- Follow your data and your team. The cloud that already holds your data and identity setup usually wins on effort and egress cost.
- Match the serving mode to traffic. Always-on endpoints suit steady traffic; serverless or batch suit spiky or offline work.
- Keep the model portable. A standard format and a thin wrapper make moving clouds a matter of rewriting the wrapper.
- Use the platform's rollout features rather than switching all traffic at once.
Common mistakes
- Testing for the first time in the cloud.
- Baking the model file into the image instead of reading it from the platform's model location.
- Returning 500 for malformed requests.
- Cloud-specific code throughout the model, instead of in one wrapper.
- Leaving idle endpoints running.
How do I deploy a machine learning model to the cloud?
What is the difference between SageMaker, Azure ML and Vertex AI deployment?
Can the same container run on more than one cloud?
How do I test a cloud model deployment locally?
Model deployment on AWS, Azure and Google Cloud is covered in the Cloud MLOps Platforms module of the MLOps course.
Sources
- Custom Inference Code with Hosting Services (Amazon SageMaker AI documentation) — Amazon Web Services
- What is Amazon SageMaker AI? (AWS documentation) — Amazon Web Services
- Custom container requirements for inference (Google Cloud documentation) — Google Cloud
- Deploy a model to an endpoint (Google Cloud documentation) — Google Cloud
- Debug with the Azure Machine Learning inference server (Microsoft Learn) — Microsoft
- Deploy and score a machine learning model by using an online endpoint (Microsoft Learn) — Microsoft
- Online endpoints for real-time inference (Microsoft Learn) — Microsoft
- Concrete Compressive Strength dataset (UCI Machine Learning Repository) — UCI Machine Learning Repository
GloryTecks Team
Training & Placement Faculty
The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.
Want to master MLOps?
Join GloryTecks’ expert-led MLOps training in Hyderabad with real-time projects and 100% placement support.
Related articles
Kubernetes for ML: Deploying Models at Scale
Kubernetes runs copies of your model service, replaces the ones that die and swaps versions without downtime, if the manifest is right. This guide deploys a concrete-strength model to a local cluster, kills a pod, rolls out a new version under continuous traffic with no failed requests, and rolls back.
Monitoring ML Models in Production: A Complete Guide
A model that passed validation can fail quietly in production. This guide sets out what to monitor at each layer, how to cope with labels that arrive late and how to design alerts people act on, using a year of real sensor data in which the loudest alert was the least useful one.
Serving ML Models with FastAPI and Docker
Putting a model behind an API is where many projects stall. This guide builds a FastAPI prediction service that loads a model once at start-up, enforces an input contract, serves single and batch predictions, reports health and readiness separately, and shows the Dockerfile that packages it.