# Share an MLflow Run Report Without Giving Out Tracking Server Access

Canonical: https://commareports.com/share-mlflow-report
Published: 2026-09-02

> The MLflow UI is a server on your network and its artifacts are objects in a bucket. Publish the run comparison as HTML so the people judging the model can actually open it.

# Share an MLflow run report

MLflow tracks the experiment properly. Then someone asks "is the new model
better?" and you discover that the answer only exists inside a server on your
network.

- **The tracking UI is yours to host.** A run URL is
  `http://mlflow.internal:5000/#/experiments/3/runs/…`, which resolves for
  people on the VPN with access to that host and for nobody else.
- **Artifacts are bucket objects.** `mlflow.log_artifact` puts your confusion
  matrix or SHAP plot in S3, GCS or a mounted volume. Reading it means
  credentials to that store, which the product manager does not have.
- **The comparison view has no shareable form at all.** The table of five runs
  side by side — the actual answer to "is it better" — exists as UI state.

So it becomes a screenshot. The screenshot loses the columns that did not fit,
and the reply comes back as "what about recall on the minority class?", which
is in the part that got cropped.

## Publish the comparison

`mlflow.search_runs` gives you the whole experiment as a DataFrame. Render it,
publish once, and update the same report at the end of every run:

```python
import os
import mlflow
import requests

runs = mlflow.search_runs(
    experiment_names=["churn-v3"],
    order_by=["metrics.val_auc DESC"],
    max_results=20,
)

cols = [
    "tags.mlflow.runName",
    "params.model",
    "params.lr",
    "metrics.val_auc",
    "metrics.val_recall",
    "metrics.train_loss",
    "start_time",
]
table = runs[cols].rename(columns=lambda c: c.split(".")[-1])

html = f"""<!doctype html><meta charset="utf-8">
<title>churn-v3 — run comparison</title>
<style>body{{font:15px/1.6 system-ui;margin:2rem;max-width:72rem}}
table{{border-collapse:collapse;width:100%}}
td,th{{border:1px solid #ddd;padding:.5rem;text-align:left}}
tr:first-child{{background:#fafafa}}</style>
<h1>churn-v3 — run comparison</h1>
<p>Top {len(table)} runs by validation AUC. Best: {table["val_auc"].max():.4f}.</p>
{table.to_html(index=False, escape=True, float_format=lambda v: f"{v:.4f}")}"""

requests.patch(
    f"https://commareports.com/api/v1/reports/{os.environ['COMMA_REPORT_ID']}",
    headers={"Authorization": f"Bearer {os.environ['COMMA_API_TOKEN']}"},
    json={"title": "churn-v3 — run comparison", "html": html},
    timeout=30,
).raise_for_status()
```

To include the plots as well, log them as usual and attach the image files as
[assets](/docs/api), or inline a self-contained Plotly figure with
`fig.to_html(full_html=False, include_plotlyjs="cdn")` — the sandbox allows
scripts and styles from jsdelivr, unpkg, cdnjs and esm.sh.

Your tracking server does not change. It stays the system of record for
parameters, metrics and lineage; the published report is the part you send to
people who are never going to open it.

## What the published copy adds

- **Readers without tracking-server access.** Visibility is
  [private, team, domain-gated, or link](/docs/sharing), decided per report.
- **It renders.** HTML is stored verbatim and served inside a sandboxed iframe
  with scripts enabled, so a Plotly ROC curve or a SHAP summary stays
  interactive — see [sharing a SHAP plot](/share-shap-plot).
- **Comments anchored to the number.** A reviewer highlights the recall column
  and pins "this is worse than the current model for the segment that matters"
  to it — see [commenting on HTML](/comment-on-html).
- **One URL across training runs.** Revisions accumulate at the same address
  and any two can be diffed, which makes "did last night's run actually help"
  a question with an answer.
- **A model card link that keeps working.** The URL outlives the tracking
  server's hostname, the VPN change and the reorg.

## Limits

- **HTML body: 5 MB.** Large figures and full artifact bundles go in as
  [assets](/docs/api) at 25 MB per file, 250 MB per report.
- **A report that fetches sibling data files at view time** can't, from the
  sandbox — inline the data or attach the archive.
- **Rate limit: 60 requests/minute per token.**

## Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision
history.

**[Create your first report →](https://commareports.com/)**

### Related

- [Share an EDA report](/share-eda-report) ·
  [Share an Evidently report](/share-evidently-report)
- [Share a SHAP plot](/share-shap-plot) ·
  [Share a Jupyter notebook as HTML](/share-jupyter-notebook-html)
- [Share an LLM eval report](/agents/share-llm-eval-report)
