Getting started
Prerequisites
Section titled “Prerequisites”- A Kubernetes cluster and Helm 3.
- For the full Nebari path: the
nebari-operator (it provides the
NebariAppCRD), 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.
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-postgresqlFor 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.
2. Install
Section titled “2. Install”helm install mlflow-pack . \ --namespace mlflow \ --set nebariapp.hostname=mlflow.example.com \ --set mlflow.postgresql.auth.existingSecret=mlflow-pack-postgresqlOn a GitOps cluster, use the Argo CD Application instead — see
Deploying on Nebari.
3. Point DNS at the hostname
Section titled “3. Point DNS at the hostname”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.
What gets deployed
Section titled “What gets deployed”| Workload | Kind | Purpose |
|---|---|---|
mlflow-pack | Deployment | MLflow server, container port 5000, service port 80 |
mlflow-pack-postgresql | StatefulSet | Backend store, 8Gi PVC |
nebari-mlflow-allowed-hosts | Secret | MLFLOW_SERVER_ALLOWED_HOSTS, injected via envFrom |
mlflow-pack-nebari-mlflow-pack | NebariApp | Routing, 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.
Verify
Section titled “Verify”kubectl -n mlflow get podskubectl -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 OKkill %1Then the routing layer:
kubectl -n mlflow get nebariappkubectl -n mlflow describe nebariappRoutingReady, 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.
- Wire up notebooks: Connecting JupyterHub
- Make artifacts durable — the default is not: Artifact storage