Kubernetes-native remote dev environments for AI/ML engineers. Edit code on your laptop, run it on cluster GPUs — with live file sync, a real shell in the pod, and first-class support for multi-pod distributed training.
Your GPUs are in a cluster. Your editor, your git history, and your muscle memory are not.
The usual workarounds all leak: kubectl cp before every run, a Docker rebuild to change one line, a Jupyter tab standing in for a terminal, or an SSH bastion that forgets everything the moment the pod restarts.
okdev makes the cluster feel like localhost. One config file, one okdev up, and your working tree is live inside a pod you can SSH into.
$ okdev up
== Ready ==
session: myproj-alice
namespace: ml-team
pod: okdev-myproj-alice-3bce18ff
ssh: ssh okdev-myproj-alice
sync: active (<->)
sync paths:
- /Users/alice/work/myproj <-> /workspace
forwards:
- app: localhost:8080 -> remote:8080$ vim train.py # edit locally, in your editor
$ okdev exec --require-sync -- python train.py # runs on the cluster, never on stale codecurl -fsSL https://raw.githubusercontent.com/acmore/okdev/main/scripts/install.sh | shPrebuilt binaries for linux and darwin on amd64 and arm64. Already installed? okdev upgrade.
cd your-repo
okdev init # writes .okdev/okdev.yaml
okdev up # creates the pod, starts sync, forwards ports
okdev ssh # you are in the cluster, in your repo
okdev down # tear it downBidirectional Syncthing-backed sync between your working tree and the pod — not a one-shot copy. okdev sync wait blocks until every byte has landed, and okdev exec --require-sync refuses to launch until it has, so a run can never silently use pre-edit code.
Sync is self-healing (it repairs broken peer connections on its own), pausable (okdev sync pause before a git checkout, so a branch switch cannot propagate onto a running job), and honest about failure — a dead channel is reported as dead rather than as "no pending changes".
okdev ssh drops you into the dev container, optionally inside a tmux-backed persistent session so a closed laptop lid does not kill a training run. Port forwards come up with the session. Your editor's remote-SSH mode works, because it is just SSH.
Run a Pod, a Job, a PyTorchJob, or any manifest you already have (Deployment, or anything else — generic). Declare several and switch between them without touching the rest of your setup:
spec:
workloads:
- name: dev # single pod for iteration
type: pod
- name: train # multi-GPU distributed run
type: pytorchjob
manifestPath: train.yaml
inject: [{path: spec.pytorchReplicaSpecs.Worker.template}]okdev workload use train && okdev up # same session, different shapeokdev injects its sidecar into every replica, wires up inter-pod SSH, and gives every pod a stable short name (master-0, worker-3) that --pod, the in-pod /etc/hosts, and MASTER_ADDR all agree on.
okdev exec --fanout 16 -- nvidia-smi # every pod, one command
okdev exec --detach -- python train.py # survives your terminal
okdev jobs logs <id> --tail 100 --grep loss # follow it later
okdev exec --reset-gpu # kill GPU holders, verify clearAt high pod counts, fanout routes through a single gateway pod over the pod network instead of N proxied apiserver streams — the difference between "works at 4 pods" and "works at 64".
spec.lifecycle.postCreate and postSync re-run automatically on every recreated pod, so installed tools and builds come back without you remembering. okdev env-diff lists what you changed by hand while debugging and drafts the hook for you.
Configure claude-code, codex, gemini, or opencode under spec.agents; okdev installs them and stages your local auth into the session container.
| Quickstart | From zero to a running session |
| Config manifest | Every field, with examples |
| Command reference | Every command and flag |
| Troubleshooting | When something is off |
Requirements: Go 1.21+, kubectl pointed at a cluster, and namespace permissions to create Pods and PVCs.
go build -o bin/okdev ./cmd/okdevFormatting is enforced by pre-commit and CI:
uv venv .venv
uv pip install --python .venv/bin/python pre-commit
.venv/bin/pre-commit install --hook-type pre-commit --hook-type pre-pushRun the same check on demand with
.venv/bin/pre-commit run --all-files --hook-stage manual okdev-gofmt.
Coverage on some Go 1.25 toolchains fails under go test ./... -cover because the
bundled covdata tool is missing for packages without tests — use
./scripts/coverage.sh instead.
For end-to-end checks against a reusable Kind cluster:
bash scripts/e2e_local_kind.shThat covers the template, smoke, deployment, job, multi-session, and
workload-switching scenarios. Sync- and reconcile-heavy changes can add
RUN_PYTORCHJOB=1, or a local-only large-repo stress check:
RUN_LARGE_REPO=1 LARGE_REPO_PATH=~/workspace/pytorch bash scripts/e2e_local_kind.sh
RUN_LARGE_REPO=1 LARGE_REPO_URL=https://github.com/pytorch/pytorch.git bash scripts/e2e_local_kind.shNever judge an e2e run through a pipe — | tail reports tail's exit status, not
the suite's. Redirect the run and capture the real one. See AGENTS.md for the
full set of repository conventions.
Apache License 2.0. See LICENSE.