Skip to main content

How to See Where MLflow Stores Runs and Artifacts

· 11 min read
Jagdish Kumawat
Founder @ Dewiride

An MLflow tracking server keeps your work in two places: a database for experiments, runs, parameters, metrics and tags, and a folder for the files your runs log, such as charts and models. With the default settings, both sit in the folder you started the server from. This guide finds them, follows one run from the UI to its files and database rows, and reads the database safely, with real output from MLflow 3.18.0.

The Layout​

A tracking server started with the default settings keeps everything in the folder you start it from, in two places:

The server's folder
mlflow-server/
├── mlflow.db experiments, runs, parameters, metrics and tags
└── mlartifacts/ the files that runs log
└── <experiment ID>/
└── <run ID>/
└── artifacts/
└── <the path you logged>
PlaceWhat it is
mlflow.dbThe backend store: a SQLite database with a row for every experiment, run, parameter, metric and tag
mlartifacts/The artifact store: an ordinary folder with a subfolder for each experiment, then one for each run

The steps below find both, follow one run into each, and look inside the database.

Prerequisites​

Step 1: Find the Two Places in the Server's Folder​

Open a terminal in the server's folder, the one you ran mlflow server from, and list what's in it:

Terminal (in mlflow-server)
ls -F

On macOS:

Output
mlartifacts/ mlflow.db pyproject.toml uv.lock
  • mlflow.db is the database, which MLflow's documentation calls the backend store: "SQLite is the default backend store. When you start MLflow without specifying a backend, it automatically creates and uses sqlite:///mlflow.db in the current directory."
  • mlartifacts/ holds the files, the artifact store: "By default, the tracking server stores artifacts in its local filesystem under ./mlartifacts directory."
  • pyproject.toml and uv.lock belong to uv, which installed MLflow in this folder.

Because both paths are relative to where the server starts, starting it from another folder gives you a new, empty database there.

Step 2: See the Locations in the UI​

The UI doesn't show folder paths. It shows addresses that start with mlflow-artifacts:. Open an experiment and click the ⓘ icon next to Runs:

The information popover on an experiment's Runs page, showing Path house-price-prediction, Experiment ID 1 and Artifact Location mlflow-artifacts:/1, each with a copy button.

The experiment's Artifact Location is mlflow-artifacts:/1, after its ID. A run's Artifacts tab shows each file's full address. For the run smiling-moose-157:

The Artifacts tab of the smiling-moose-157 run with the plots folder open and predicted_vs_actual.png selected, its size 90.13KB, and its path mlflow-artifacts:/1/8dbe08fc91434c5b9b22456dcd740ebf/artifacts/plots/predicted_vs_actual.png.

mlflow-artifacts:/ means "the server's artifact store". Your code never writes into mlartifacts itself: it sends files to the server, and the server stores them. In the documentation's words, "The tracking server resolves the uri mlflow-artifacts:/ in tracking request from the client to an otherwise explicit object store destination", here the local mlartifacts folder.

Step 3: Follow a Run to Its Files​

List every file in the artifact store:

Terminal (in mlflow-server)
find mlartifacts -type f

On macOS:

Output
mlartifacts/1/09c7c9012ca74eafaf16c8766226d6b7/artifacts/plots/predicted_vs_actual.png
mlartifacts/1/7cb465b94f2e426f9af5a12c55326cbe/artifacts/plots/predicted_vs_actual.png
mlartifacts/1/8dbe08fc91434c5b9b22456dcd740ebf/artifacts/plots/predicted_vs_actual.png
mlartifacts/1/ec7ccb942d66407490b3aee0cb4d9b62/artifacts/plots/predicted_vs_actual.png

PowerShell prints the same paths with ./ in front on macOS; on Windows they start with .\ and use backslashes, as in Microsoft's Resolve-Path examples.

Every file follows the same pattern:

Artifact path
mlartifacts/<experiment ID>/<run ID>/artifacts/<the path you logged>

The third line is the file from Step 2: experiment 1, run 8dbe08fc91434c5b9b22456dcd740ebf (smiling-moose-157), and plots/predicted_vs_actual.png, the path the training code gave mlflow.log_figure(). Replace mlflow-artifacts: with mlartifacts and the UI's address becomes the file's location on disk.

Step 4: Look Inside mlflow.db​

Python 3.12 added a command line to its built-in sqlite3 module: python -m sqlite3 <database> "<query>" runs one query and prints each row. It needs nothing extra to install, and the commands below are the same in every shell.

Start with the experiments:

Terminal (in mlflow-server)
uv run python -m sqlite3 mlflow.db "SELECT experiment_id, name, artifact_location FROM experiments;"
Output
(0, 'Default', 'mlflow-artifacts:/0')
(1, 'house-price-prediction', 'mlflow-artifacts:/1')

Default is experiment 0, which MLflow creates when it sets up the database. Next, the runs in experiment 1:

Terminal (in mlflow-server)
uv run python -m sqlite3 mlflow.db "SELECT name, status, artifact_uri FROM runs WHERE experiment_id = 1;"
Output
('smiling-moose-157', 'FINISHED', 'mlflow-artifacts:/1/8dbe08fc91434c5b9b22456dcd740ebf/artifacts')
('50-trees-depth-8', 'FINISHED', 'mlflow-artifacts:/1/ec7ccb942d66407490b3aee0cb4d9b62/artifacts')
('200-trees-depth-16', 'FINISHED', 'mlflow-artifacts:/1/09c7c9012ca74eafaf16c8766226d6b7/artifacts')
('300-trees', 'FINISHED', 'mlflow-artifacts:/1/7cb465b94f2e426f9af5a12c55326cbe/artifacts')

Each run's artifact_uri is its folder in the artifact store. Now one run's parameters, metrics and tags, here 300-trees. Use a run ID of your own, from the query above or the run page:

Terminal (in mlflow-server)
uv run python -m sqlite3 mlflow.db "SELECT key, value FROM params WHERE run_uuid = '7cb465b94f2e426f9af5a12c55326cbe';"
uv run python -m sqlite3 mlflow.db "SELECT key, value, step FROM metrics WHERE run_uuid = '7cb465b94f2e426f9af5a12c55326cbe';"
uv run python -m sqlite3 mlflow.db "SELECT key, value FROM tags WHERE run_uuid = '7cb465b94f2e426f9af5a12c55326cbe';"
Output
('n_estimators', '300')
('max_depth', 'None')
('test_size', '0.2')
('random_state', '42')
('mae', 0.3267739603762923, 0)
('rmse', 0.503821166676639, 0)
('r2', 0.8062927130336361, 0)
('mlflow.user', 'you')
('mlflow.source.name', '/Users/you/source/dewiride-house-price-prediction/.venv/bin/train')
('mlflow.source.type', 'LOCAL')
('mlflow.runName', '300-trees')
('model_type', 'random_forest')
('dataset', 'california_housing')

A macOS-style terminal window running three sqlite3 queries against mlflow.db for the 300-trees run: its four parameters stored as text, its three metrics with full precision and step 0, and its tags, including the system tags mlflow.user, mlflow.source.name, mlflow.source.type and mlflow.runName.

  • Parameters are text: '300', even 'None'. That's why parameters compare as text in searches.
  • Metrics are numbers at full precision, each with a step, 0 when none was given. The UI rounds them for display.
  • Tags include MLflow's system tags, such as mlflow.user and mlflow.source.name, next to the run's own model_type and dataset.

The metrics table keeps every value ever logged. A second table, latest_metrics, keeps one row per run and metric name:

Terminal (in mlflow-server)
uv run python -m sqlite3 mlflow.db "SELECT key, value, step FROM latest_metrics WHERE run_uuid = '7cb465b94f2e426f9af5a12c55326cbe';"
Output
('mae', 0.3267739603762923, 0)
('rmse', 0.503821166676639, 0)
('r2', 0.8062927130336361, 0)

The same three values here, because each metric was logged once. For a metric logged at many steps, such as a training loss, metrics holds a row per step, and latest_metrics keeps the one with the highest step.

What the Other Tables Hold​

The database has many more tables than these:

Terminal (in mlflow-server)
uv run python -m sqlite3 mlflow.db "SELECT count(*) FROM sqlite_master WHERE type = 'table';"
Output
(71,)

MLflow creates all 71 when it sets up a new database, whether you use them or not. The ones for experiment tracking:

TableWhat it holds
experimentsOne row per experiment: its ID, name and artifact location
experiment_tagsTags on experiments
runsOne row per run: its ID, name, status, times and artifact URI
paramsRun parameters, as text
metricsEvery logged metric value, with its step and time
latest_metricsThe latest value of each metric, one row per run and metric name
tagsRun tags, including MLflow's system tags
datasets, inputs, input_tagsDatasets logged as run inputs
logged_models and the logged_model_… tablesModels logged with MLflow 3, with their own parameters, metrics and tags
registered_models, model_versions and their …_tags and …_aliases tablesThe Model Registry
alembic_versionThe version of the database layout, which mlflow db upgrade uses

Most of the rest serve MLflow's GenAI features, such as traces (trace_info, spans) and evaluations (assessments, scorers). List every name with SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;.

Read the database, never edit it

Every query on this page only reads. Changing rows by hand can leave the database inconsistent with the files in mlartifacts, or with what MLflow expects. Change runs through MLflow: its Python API, its command line or the UI.

Common Mistakes​

MistakeWhat happensDo this instead
Starting the server from another folderA new, empty mlflow.db and mlartifacts appear there, and your runs seem to vanishAlways start the server from the same folder
Logging with no tracking URI setThe run goes to an mlflow.db in your project's folder, not the server'sSet the tracking URI: How to Set the MLflow Tracking URI in Python
Looking for files in mlflow.dbThe database only stores the files' locationLook in mlartifacts/<experiment ID>/<run ID>/artifacts
Editing mlflow.db by handThe data can end up inconsistentChange runs through MLflow

Troubleshooting​

SymptomLikely causeFix
mlartifacts doesn't existNo run has logged a file yetLog one; the folder appears with the first file
A run is in the UI but its files aren't in mlartifactsThe run was logged to another server or folderCompare the run's artifact address with the server you started

FAQ​

Is it safe to query mlflow.db while the server is running?​

For reading, yes. Every query on this page ran while the server was running. Don't write to it.

Where do runs go when I don't run a server?​

With no tracking URI set, MLflow 3.18 logs to an mlflow.db in the folder you run your code from, and puts files in an mlruns folder there: How to Set the MLflow Tracking URI in Python.

What happens to mlflow.db when I upgrade MLflow?​

A newer MLflow can need a newer database layout. MLflow's documentation says "mlflow server will fail against a database with an out-of-date schema", and to run mlflow db upgrade after backing the database up.

Conclusion​

The tracking server kept everything in the folder it started from: mlflow.db, a SQLite database with experiments, runs, parameters, metrics and tags, and mlartifacts, with each run's files under <experiment ID>/<run ID>/artifacts. The UI's mlflow-artifacts:/1/… addresses pointed at the same files, and python -m sqlite3 read the database without changing it.

Additional Resources​