@j43@seer.cxrss

logs

short-lived workspaces

a workspace pod should not be a server. it should be a socket. vmrun is a go cli that opens a kubernetes job, sshes into it, and lets the pod die when you walk away. nothing long-lived, no operator, no controller, no webhook.

the rule

every workspace is a batch/v1 job with a hard deadline. ttl is one minute to two hours, default thirty. ttlSecondsAfterFinished is thirty. when the client disconnects, the job controller does the cleanup.

the cli only edits jobs. it never creates pods, writes secrets, executes commands, or touches another namespace.

this is the shape of the rule: submit a job, wait for the pod, ssh through the api port-forward, leave, let the deadline finish what you started.

vmrun create scratch --size small --ttl 30m
vmrun ssh scratch
vmrun scp file scratch:/home/dev/
vmrun extend scratch 30m
vmrun delete scratch

three planes

the design separates where coordination happens from where traffic happens. tailscale or headscale sits on the coordination plane and does not carry workspace bytes. wireguard sits on the transport plane and tunnels only to the kubernetes api. the kubernetes api itself carries the ssh stream inside a port-forward, so the pod is never reachable from the network. only the api server, which has already authenticated the client, can reach it.

COORDINATION PLANEtailscale saas or self-hosted headscalepeer maps · public keys · endpoints · policykeyskeysoperator workstationsova host · k0s v1.36.1transport plane · wireguardhost tailscaledshort-livedkubeconfigssh keypairoperator pubkeyvmrun clicreate · list · extend · delete · scpopenssh / scp127.0.0.1:NNNNhost tailscaledk0s kubernetes api :6443mTLS authentication · port-forward streamsecurity gatesrbac · admissionquota · networkpolicydigest-pinned imagettl: 1m–2h · default 30m≤2 jobs · 1500m · 3GiBbatch/v1 Jobhard lifetimeworkspace podsshd :2222 · user devapi + port-forwardport-forwardinternet ──x──▶ podlan ──x──▶ podtailnet peer ──x──▶ pod

three planescoordinationtransportworkspace

inbound pathsthat do notexist

the discipline

the client is a kubectl rival with one verb: job. the namespace permits at most two jobs and two pods. the validating admission policy fails closed on job shape (canonical labels, the exact digest-pinned image, one container, no init, no sidecar, no volume, no secret, no service account token, no host network). the network policy denies all ordinary ingress; the api port-forward is authorized through rbac, not through a pod rule.

egress is the conversation the workspace is allowed to have: dns to cluster dns, public ipv4 tcp for packages, git, and mail. it cannot reach the pod network, the service network, the lan, the loopback, link-local, cgnat, multicast, or non-dns udp. the kernel is shared. sudo is passwordless. this is container isolation, not hostile-code isolation. deliberately hostile workloads should use kubevirt or another hardware-backed runtime.

activeDeadlineSeconds: 1800
backoffLimit: 0
ttlSecondsAfterFinished: 30

when the client crashes, the deadline fires, the pod stops, and the cleanup ttl finishes the work. the cli is not in the data path after submit. there is no Workspace crd, no controller, no webhook, no long-running service.

the simplest possible server is a job that ends on time.

the connection path

remote workstation
  → host tailscale
  → sova.tailnet:6443
  → mtls and rbac
  → pods/portforward stream
  → workspace pod sshd :2222
  → zsh as dev

the byte stream rides the kubernetes api from start to finish. the cli opens a websocket port-forward, falls back to spdy when required, binds a random loopback port, and runs openssh with a temporary config that aliases the host key by job uid. a recreated workspace with the same name does not silently inherit the previous identity.

the legacy vmrun-gateway deployment sits at zero replicas. it has no oauth secret, no pvc, no running pod. it is not part of the data path. it is a reminder that the rule is enforced by absence, not by configuration.

the network is unreachable. the network is exactly the api.