Skip to content

Getting started

Updated 2 min read

  • A Kubernetes cluster and Helm 3.
  • For the full Nebari path: the nebari-operator (it provides the NebariApp CRD), Envoy Gateway, cert-manager with a cluster issuer, and a Keycloak realm.
  • A StorageClass for the PostgreSQL PVC.

To try MLflow without any of the Nebari pieces, skip to Standalone deployment.

1. Create the PostgreSQL credentials secret

Section titled “1. Create the PostgreSQL credentials secret”

The bundled PostgreSQL reads its passwords from a Secret you create in advance.

Terminal window
kubectl create namespace mlflow
kubectl create secret generic mlflow-pack-postgresql \
--namespace mlflow \
--from-literal=password="$(openssl rand -base64 32)" \
--from-literal=postgres-password="$(openssl rand -base64 32)"

Then reference it:

mlflow:
postgresql:
auth:
existingSecret: mlflow-pack-postgresql

For a throwaway cluster you can skip the secret and pass the password inline — --set mlflow.postgresql.auth.password=dev-only — but never in production or a GitOps repository. More detail in PostgreSQL backend.

Terminal window
helm install mlflow-pack . \
--namespace mlflow \
--set nebariapp.hostname=mlflow.example.com \
--set mlflow.postgresql.auth.existingSecret=mlflow-pack-postgresql

On a GitOps cluster, use the Argo CD Application instead — see Deploying on Nebari.

Add an A or CNAME record for mlflow.<your-domain> pointing at the gateway’s external address. The certificate is not your job: when the operator has a ClusterIssuer configured, the NebariApp has cert-manager issue one for the hostname and adds a per-app HTTPS listener to the gateway. Until DNS resolves, the ACME challenge cannot complete and TLSReady stays False with reason CertificateNotReady.

If the operator has no ClusterIssuer, it falls back to the gateway’s shared HTTPS listener — and then the hostname does have to be covered by that shared certificate. To use a certificate you manage yourself, set nebariapp.routing.tls.secretName to a kubernetes.io/tls secret in envoy-gateway-system.

WorkloadKindPurpose
mlflow-packDeploymentMLflow server, container port 5000, service port 80
mlflow-pack-postgresqlStatefulSetBackend store, 8Gi PVC
nebari-mlflow-allowed-hostsSecretMLFLOW_SERVER_ALLOWED_HOSTS, injected via envFrom
mlflow-pack-nebari-mlflow-packNebariAppRouting, TLS, and Keycloak client

Note the service is named after the release, not <release>-mlflow — the community chart’s fullname helper collapses when the release name contains the chart name. The nebariapp.service.name default follows the same helper, so the two always agree.

The NebariApp gets the long name for the opposite reason: this chart’s fullname helper does not collapse, because the release name mlflow-pack does not contain the chart name nebari-mlflow-pack, so the two are concatenated. Everything the operator derives inherits it — the OIDC client secret is mlflow-pack-nebari-mlflow-pack-oidc-client, the certificate is mlflow-pack-nebari-mlflow-pack-mlflow-cert. The commands in these docs leave the name off wherever they can.

Terminal window
kubectl -n mlflow get pods
kubectl -n mlflow rollout status deployment/mlflow-pack
# Health endpoint, straight at the pod. /health and /version are exempt from
# MLflow's Host-header check, so this answers even if the allowed-hosts list is wrong.
kubectl -n mlflow port-forward svc/mlflow-pack 5080:80 &
curl -sf http://localhost:5080/health && echo OK
kill %1

Then the routing layer:

Terminal window
kubectl -n mlflow get nebariapp
kubectl -n mlflow describe nebariapp

RoutingReady, TLSReady, and AuthReady should all be True. If they are not, Troubleshooting maps each one to its usual cause.

Open https://mlflow.example.com. Keycloak takes the login, then MLflow’s UI loads.