Back to Blog
Tutorial9 min read2026-07-18

Deploy an MLflow Tracking Server on PandaStack (2026)

MLflow tracks your ML experiments, parameters, metrics, and model artifacts in one place. Here's how to run a persistent MLflow tracking server on PandaStack backed by managed PostgreSQL and object storage.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Machine learning without experiment tracking is a spreadsheet of model_final_v3_REALLY_final.pkl files and a vague memory of which learning rate worked. MLflow (https://mlflow.org) fixes that: a tracking server that records every run's parameters, metrics, and artifacts, plus a model registry for promoting versions to staging and production. This guide runs a remote MLflow tracking server on PandaStack so your whole team logs to one shared, durable place instead of a local mlruns/ folder that dies with the laptop.

Architecture: the three storage layers

A production MLflow server needs three things, and understanding them prevents the classic "my metrics vanished" incident:

  1. 1A backend store for runs, params, and metrics → PostgreSQL (managed on PandaStack). The default file/SQLite store doesn't survive restarts and can't be shared.
  2. 2An artifact store for models, plots, and large files → object storage (S3-compatible). Artifacts are too big for the database.
  3. 3The tracking server itself → a container app on PandaStack that ties them together and serves the UI.

Step 1: Dockerfile

FROM python:3.12-slim

WORKDIR /app
RUN pip install --no-cache-dir \
    mlflow>=2.14 \
    psycopg2-binary>=2.9 \
    boto3>=1.34

EXPOSE 5000

COPY start.sh /start.sh
ENTRYPOINT ["/bin/sh", "/start.sh"]

start.sh:

#!/bin/sh
exec mlflow server \
  --host 0.0.0.0 \
  --port 5000 \
  --backend-store-uri "$MLFLOW_BACKEND_STORE_URI" \
  --artifacts-destination "$MLFLOW_ARTIFACT_ROOT" \
  --serve-artifacts

--serve-artifacts proxies artifact uploads/downloads through the server, so your clients only need one URL and don't each need direct object-storage credentials — much simpler and more secure for a team.

Step 2: Create the PostgreSQL backend store

Dashboard (https://dashboard.pandastack.io) → Databases → New Database → PostgreSQL. Your backend store URI looks like:

postgresql://user:password@host:5432/mlflow_db

Step 3: Provide object storage for artifacts

Point --artifacts-destination at an S3-compatible bucket, e.g. s3://your-bucket/mlflow. Supply credentials as env vars. If your bucket is on an S3-compatible provider, also set the endpoint URL.

Step 4: Deploy

  1. 1Push the repo to GitHub.
  2. 2New App → Container App, connect the repo, container port 5000.
  3. 3Environment variables:
VariableValue
MLFLOW_BACKEND_STORE_URIyour Postgres URI
MLFLOW_ARTIFACT_ROOTs3://your-bucket/mlflow
AWS_ACCESS_KEY_IDobject-storage key
AWS_SECRET_ACCESS_KEYobject-storage secret
MLFLOW_S3_ENDPOINT_URLyour S3-compatible endpoint (if not AWS)
  1. 1Deploy. The MLflow UI is live at https://your-mlflow.pandastack.io.

CLI:

npm install -g @pandastack/cli
panda login
panda deploy

Step 5: Log from your training code

Point your training scripts at the remote server via MLFLOW_TRACKING_URI:

import mlflow

mlflow.set_tracking_uri("https://your-mlflow.pandastack.io")
mlflow.set_experiment("churn-model")

with mlflow.start_run():
    mlflow.log_param("learning_rate", 0.01)
    mlflow.log_param("max_depth", 6)

    # ... train ...

    mlflow.log_metric("auc", 0.91)
    mlflow.log_metric("accuracy", 0.88)

    mlflow.sklearn.log_model(model, "model")

Every teammate who sets the same tracking URI logs into the same shared history. Compare runs side by side in the UI, sort by metric, and see exactly which hyperparameters produced which result.

Step 6: Use the model registry

Promote a good run's model into the registry and move it through stages:

result = mlflow.register_model(
    "runs:/<run_id>/model",
    "churn-model",
)

from mlflow import MlflowClient
client = MlflowClient()
client.transition_model_version_stage(
    name="churn-model", version=result.version, stage="Production",
)

Your serving code then loads whatever is currently in Production without hardcoding a run ID:

model = mlflow.pyfunc.load_model("models:/churn-model/Production")

Step 7: Protect the server

An open MLflow server on the public internet lets anyone read your experiments and download models. Before you log anything sensitive:

  • MLflow supports basic auth — set it up so clients must authenticate.
  • Or place the server behind your own auth (an edge function or reverse proxy that checks a token).
  • At minimum, keep the tracking URL out of public repos and rotate object-storage credentials if they ever leak.

Step 8: Back up the backend store

Your entire experiment history is in Postgres. A scheduled PandaStack cronjob running pg_dump to object storage on 0 3 * * * is cheap insurance. Artifacts already live in durable object storage, so backing up the database covers the rest.

When MLflow is overkill

If you run occasional experiments solo, a local mlruns/ folder is genuinely fine and needs zero infrastructure. Stand up the remote server when multiple people need shared history, or when you want a model registry driving what gets served. Match the machinery to the team size.

Wrap-up

A remote MLflow server turns ad-hoc training runs into a searchable, shared, promotable history. On PandaStack it's a container app wired to managed Postgres and object storage — just remember all three layers, and lock down access before real work goes in. Docs: https://docs.pandastack.io. Start free at https://dashboard.pandastack.io.

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also