PostgreSQL backend
MLflow’s backend store holds every run, parameter, metric, tag, and the model registry. This chart enables the bundled PostgreSQL by default because the alternative loses all of it on restart.
mlflow: postgresql: enabled: true image: tag: "17.5.0" auth: username: mlflow database: mlflow existingSecret: "" primary: persistence: enabled: true size: 8GiWhen postgresql.enabled is true the upstream chart wires PGHOST, PGPORT, PGDATABASE,
PGUSER, and PGPASSWORD into the MLflow container and sets
--backend-store-uri=postgresql:// on the server. Nothing to configure by hand.
The credentials secret
Section titled “The credentials secret”kubectl create secret generic mlflow-pack-postgresql \ --namespace mlflow \ --from-literal=password="$(openssl rand -base64 32)" \ --from-literal=postgres-password="$(openssl rand -base64 32)"mlflow: postgresql: auth: existingSecret: mlflow-pack-postgresqlInline passwords
Section titled “Inline passwords”For a throwaway cluster:
helm install mlflow-pack . --set mlflow.postgresql.auth.password=dev-onlyNever in production, and never in a values file committed to a GitOps repository — Helm values are readable by anyone with access to the release.
Rotating the password
Section titled “Rotating the password”Update the Secret, then restart both workloads:
kubectl -n mlflow rollout restart statefulset/mlflow-pack-postgresqlkubectl -n mlflow rollout restart deployment/mlflow-packChanging the Secret alone changes nothing — the values are read into the environment at pod start.
Storage
Section titled “Storage”The default 8Gi PVC is generous for metadata: runs, params, and metrics are small rows. Growth comes from run volume, not model size — models live in the artifact store.
Set the storage class explicitly on any cluster where the default is not what you want. PVCs cannot be reassigned to a different class after creation:
mlflow: postgresql: primary: persistence: size: 20Gi storageClass: fast-ssdMigrations
Section titled “Migrations”mlflow: backendStore: databaseMigration: true databaseConnectionCheck: truedatabaseMigration runs MLflow’s schema migration at startup, so a chart upgrade that
bumps MLflow also updates the database. databaseConnectionCheck adds an init container
that waits for PostgreSQL to accept connections, avoiding a crash-loop while the database
is still starting.
Backups
Section titled “Backups”The chart does not back up the database. On a Nebari cluster with longhorn-backup-pack installed, the PostgreSQL PVC is covered by the cluster-wide schedule if it is on the default StorageClass — worth confirming rather than assuming:
kubectl -n longhorn-system get volumes.longhorn.io \ -l recurring-job-group.longhorn.io/default=enabledFor a logical backup:
kubectl -n mlflow exec -i statefulset/mlflow-pack-postgresql -- \ pg_dump -U mlflow mlflow > mlflow-backup.sqlNo -t there: a TTY rewrites line endings in the redirected dump.
A complete restore needs both halves — the database and the artifact bucket. Restoring one without the other produces runs that reference missing models, or orphaned files no run points at.
Using an external PostgreSQL
Section titled “Using an external PostgreSQL”Disable the bundled instance and supply a connection string:
mlflow: postgresql: enabled: false backendStore: postgres: enabled: true host: postgres.example.com port: 5432 database: mlflow user: mlflow # password: ... — or omit it and set # backendStore.existingDatabaseSecret.{name,usernameKey,passwordKey}Check the community chart values for the exact shape — that block belongs to the subchart, not this one.
The SQLite fallback
Section titled “The SQLite fallback”mlflow: postgresql: enabled: falseMLflow then falls back to SQLite — and not to a file, either. The upstream chart’s
backendStore.defaultSqlitePath is :memory:, so the server runs with
--backend-store-uri=sqlite:///:memory:. Everything is lost when the process restarts,
along with the artifacts, and with more than one server worker each would see its own empty
database. Fine for a five-minute demo or CI — the chart’s own test workflow uses it — and
nothing else.
examples/standalone-values.yaml uses this combination deliberately; see
Standalone deployment.