DreamLake

Kube Provider

--launcher Kube submits Pods to an existing Kubernetes cluster. Nothing is provisioned — the cluster is assumed to be there. The default dispatch is direct: creating the Pod is the launch.

PhaseWhat happens
ProvisionNone.
LaunchOne Pod created through @kubernetes/client-node, restartPolicy: Never.
Default dispatchdirect

Where the launch happens

Kube is the one launcher with two working paths, and they differ in who holds the kubeconfig.

Who creates the PodKubeconfig lives
Control plane (default)@kubernetes/client-node, server-sidein the control plane's Secret store
A nymph in the clusterkubectl apply, from runner/kube.rson the box the nymph runs on

The second path is the same shape SLURM uses: a nymph that can reach the scheduler does the submitting itself, so the credential never leaves the cluster. See Running a daemon in-cluster for when to prefer it — and for the hostPath constraint that limits it to single-node clusters today.

Kube, not K8s

The spelling is enforced. k8s, K8S, and local are all rejected with a 400 — some of those appear in stale comments in the Prisma schema. The launcher name is exactly Kube.

Kwargs

Connection (usually on the provider):

FieldDefaultMeaning
kubeconfig—A filesystem path, or inline kubeconfig content, or a { "$secret": … } ref. Omit to use the server's KUBECONFIG / ~/.kube/config.
context—Switches the active context after loading.
namespacedefaultThe Kubernetes namespace the Pod is created in.

Pod shape (usually on the mode):

FieldRequiredMeaning
imageyesContainer image. Missing or empty fails the launch immediately.
resourcesnoPassed straight through as the container's resources block — so it takes the real Kubernetes shape, { requests: {...}, limits: {...} }.
envnoObject of name → value. Numbers and booleans are stringified; anything else is skipped.
node_selectorno→ spec.nodeSelector.
tolerationsno→ spec.tolerations, verbatim list.
service_accountno→ spec.serviceAccountName.
image_pull_secretsnoList of names → spec.imagePullSecrets.

The Pod runs a single container named user with command: ["/bin/sh", "-c"].

The Kube launch script is Python, not bash

EC2 and GCE take the launch script as a bash userdata blob. The Kube launcher instead heredocs it into /tmp/u.py inside the container and runs python3 /tmp/u.py, so the image needs a python3 on PATH. This is the one launcher where the script contract differs.

Labels always include lakeshore=true, lakeshore.namespace=<ns>, and lakeshore.provider=<name> (slugified). Launch-body tags become extra labels; anything colliding with the three reserved keys is dropped. The instances listing selects on exactly those three.

Pod names are always auto-generated as lakeshore-<8 hex> — a name kwarg is not read by this launcher.

Example

.dreamrc tab: The same shape in a local .dreamrc:

bash
lakeshore providers add my-cluster --launcher Kube \
  --kwarg context=prod-east \
  --kwarg namespace=lakeshore

lakeshore modes add k8s-gpu \
  --field provider=my-cluster \
  --field image=ghcr.io/lakeshore-py/cuda12.4-pytorch:2.4 \
  --field resources='{"limits":{"nvidia.com/gpu":1,"cpu":"4"}}' \
  --field node_selector='{"cloud.google.com/gke-accelerator":"nvidia-tesla-a100"}'

Credentials

Same as kubectl. With no kubeconfig kwarg the server loads its default config (KUBECONFIG or ~/.kube/config). A kubeconfig string that looks like a path (starts with /, ./, or ~ and has no newlines) is loaded from disk; anything else is treated as inline kubeconfig content and staged to a temp file. A { "$secret": … } ref resolves to plaintext, gets written to a chmod-600 temp file for the duration of the SDK call, and is cleaned up in a finally. See Secrets.

Server-side routes

VerbPathResult
POST/v1/namespaces/:ns/providers/:name/launch202 { instanceId, state, providerName, launchedAt }
GET/v1/namespaces/:ns/providers/:name/instancesPods matching the three reserved labels
DELETE/v1/namespaces/:ns/providers/:name/instances/:id204 — deleting an already-gone Pod is idempotent

instanceId is the Pod name. state comes from the Pod at create time: a deletionTimestamp means stopping regardless of phase, otherwise Pending → starting, Running → running, Succeeded/Failed → stopped, anything else unknown. At create time it is normally starting; poll instances to watch it become running.

Running a daemon in-cluster

Set dispatch: daemon when the cluster cannot reach the control plane, when you want every launch to flow through one in-cluster service you can audit, or when you would rather not hand the control plane a kubeconfig with broad permissions.

Note that nymph also has its own kube runner, which is a different thing: a daemon already running can render a Pod manifest and kubectl apply it per invocation. That runner mounts the daemon's workdir into the Pod with a hostPath volume, so it only works when the daemon and the Pod land on the same node — a single-node kind / minikube / k3d cluster, or a DaemonSet. Multi-node needs an RWX volume, which is not built.

Smoke test

bash
lakeshore providers test my-cluster
lakeshore providers instances my-cluster
kubectl get pods -n lakeshore

Streaming stdout from the Pod back to the CLI is not implemented — read it with kubectl logs.