BYOC Kubernetes onboarding
Run the PlatformSmith controller and sandbox pods inside your own existing Kubernetes cluster — on-prem, EKS, AKS or GKE — while the hosted control plane stays where it is.
Install is a single kubectl apply of a manifest we render for your environment: no Helm, no
CRD, no operator. The control plane is egress-only (WSS on 443) with zero inbound, and
PlatformSmith never holds a credential for your cluster.
What you get
An environment whose controller lives in your cluster. PlatformSmith dispatches launch tasks to it over the same WebSocket channel a Docker controller uses; your controller creates sandbox pods in a namespace you own.
The manifest also installs the environment's isolation layer, applied fresh on every install: a NetworkPolicy scoping who can reach your controller, a NetworkPolicy blocking sandboxes from cloud metadata endpoints, Pod Security labels, and a non-root, read-only controller. See Security posture for exactly what that does and does not guarantee.
- Topology: shown as Kubernetes;
k8s_byocin the API. - Install unit: a plain-YAML manifest with every per-environment value already baked in. You apply it; you never fill in blanks.
- Blast radius: one pair of namespaces. Namespace-scoped RBAC, no cluster-wide grants.
Prerequisites
| Requirement | Why |
|---|---|
| Kubernetes 1.30 or newer | The manifest pins Pod Security Admission's enforce-version to v1.30. |
An existing cluster you can kubectl apply to | You bring the cluster; PlatformSmith provisions nothing. |
kubectl configured against it | The manifest is applied with your own credentials — they never leave your machine. |
| Permission to create a Namespace, ServiceAccount, Role, RoleBinding, Deployment, Service, NetworkPolicy and Secret | That is the entire object set. No cluster-admin, no CRD install. |
| Egress 443 (WSS) from the cluster to PlatformSmith | The controller dials out. This is the only network requirement in either direction. |
| A default StorageClass | Sandboxes persist their workspace on a PVC using your cluster's default class. |
You do not need: Helm, a chart repo, an ingress controller, a pull secret (the controller and sandbox images are published publicly), or any inbound path into your cluster.
Happy path
1. Create the environment
Creating an environment is a single step: name it, and choose Kubernetes as where its agents run. The install command appears beneath your choice.
You do not fill in a namespace or a storage class — the two namespace names are derived from the environment's name and shown to you read-only, and the manifest uses your cluster's default storage class.
You can switch between Host with Docker and Kubernetes as often as you like and it updates the same environment — its ID does not change — right up until a controller connects. After that the type is locked and a switch is refused.
Not ready? Connect it later saves the environment unconnected, and the install command stays available from the environment's Attach controller action.
Creating the environment mints nothing by itself. Generating the install command mints only a single-use code; the controller's own credential is not minted until that code is exchanged, which happens when the command actually runs.
2. Run the install command
curl -fsS -H "Authorization: Bearer <code>" <url> | kubectl apply -f -
<code> is a single-use install code (psic_…), minted when the command is generated and valid for
15 minutes. Generate new command mints another when it lapses.
The code travels as an Authorization header, never as a URL parameter — deliberately. Do not
reconstruct it into a link and paste it into chat: a link preview that fetches it would burn the single
use and hand whoever generated the preview a manifest containing a live controller credential.
Exchanging the code renders the manifest and mints a fresh controller token bound to this environment. Once the new controller connects, the previous controller token stops working — so to reinstall or upgrade, generate a new command rather than re-applying a manifest you saved earlier.
3. The controller dials out and registers
The controller Deployment boots, authenticates to its own API server with the ServiceAccount
token the kubelet mounts for it, and dials out over WSS to PlatformSmith with the token baked into
the manifest. No inbound path into your cluster is opened at any point.
4. Sandboxes land in <env>-sandboxes
When you launch work, PlatformSmith routes the spawn to your controller by name, and the controller
creates the pod in the <env>-sandboxes namespace. Whether a runtime is a Docker container or a
Kubernetes pod is invisible to the code running inside it.
What the manifest contains
A fixed, transparent object set — you can read all of it before applying.
| Object | Namespace | Purpose |
|---|---|---|
Namespace × 2, with Pod Security labels | — | <env>-system (the controller) and <env>-sandboxes (runtime pods) |
NetworkPolicy controller-ingress | <env>-system | Ingress-only. TCP 9002 from <env>-sandboxes, plus opted-in namespaces for deployment-role environments |
NetworkPolicy sandbox-egress-baseline | <env>-sandboxes | Egress-only. Blocks 169.254.0.0/16 (all cloud metadata endpoints), the Azure WireServer and AWS's IPv6 metadata range; re-allows NodeLocal DNSCache |
ServiceAccount default | <env>-sandboxes | automountServiceAccountToken: false — sandbox pods get no Kubernetes API token |
ServiceAccount ps-controller + Role + RoleBinding | <env>-system / <env>-sandboxes | Namespace-scoped least privilege: create/get/delete pods, PVCs, services; create/delete ingresses; create/get/patch/delete secrets. No list/watch, no ClusterRole, and never a networkpolicies verb |
Deployment ps-controller | <env>-system | Non-root (runAsUser: 65532), read-only root filesystem, no privilege escalation, all capabilities dropped. Image pinned by digest, never a moving tag |
Service (ClusterIP :9002) | <env>-system | How runtimes reach the controller in-cluster. ClusterIP only |
Secret | <env>-system | The controller token minted for this install |
No Ingress is in the default render — previews reach the sandbox pod through its own Service.
Security posture
Enforced by the manifest:
- Per-environment network rules. Who can reach your controller on 9002 is limited to this environment's own sandboxes namespace. Sandboxes cannot reach link-local addresses, including every cloud metadata endpoint.
- No Kubernetes API access from sandboxes. No sandbox pod gets a token for your API server.
- Pod Security. Both namespaces enforce the strictest level each pod kind can meet.
- A non-root controller that cannot loosen its own isolation — its Role has no
networkpoliciesverb. - No cluster credentials leave your organization. The controller authenticates to its own API server with the token the kubelet mounts. No kubeconfig, ServiceAccount token or cloud IAM credential is ever uploaded to, stored by, or seen by PlatformSmith.
Depends on your cluster:
- NetworkPolicy only works if your CNI enforces it. The same policies are rendered on every cluster; PlatformSmith cannot verify that yours applies them. Calico, Cilium, GKE Dataplane V2, the EKS VPC CNI policy agent and AKS network policy all do — plain kubenet does not.
Not done by this manifest:
- Domain allowlisting. Sandboxes can reach any non-link-local address; nothing restricts which external hosts.
- Full private-range egress blocking. Only link-local and metadata ranges are blocked, not every RFC 1918 address.
- Sandboxes have no
sudo.
Previews
The sandbox policy is egress-only, so nothing blocks inbound to a sandbox pod and previews work. The
control plane is 100% egress-only and never needs ingress; the only thing that could need inbound
is your own app port — a dev server on :3000 — reaching a browser.
By default the render contains no Ingress and needs no ingress controller or wildcard DNS. If you
opt into previews you supply your own ingress controller and domain, and the controller creates a
per-sandbox Service and Ingress. That is a separate network plane and never opens inbound to the
control plane.
Update and uninstall
Updating is re-applying. Generate a fresh install command — it mints a new code and a new controller token every time — and run it again.
- New objects are added; existing namespace labels are kept.
- The controller token rotates; the previous one stops working once the new controller connects.
- Controller and runtime move in lockstep. Sandboxes an older controller already created keep working, unchanged, until they end.
- Expect a brief window during the rollout where new launches can fail while the old controller pod terminates. Already-running sandboxes are not disrupted.
Uninstalling is deleting the two namespaces — every object is namespaced, nothing is cluster-scoped:
kubectl delete namespace <env>-system <env>-sandboxes
That removes the sandboxes running in them and their PVCs. There is no manifest to fetch first (which would burn an install code for nothing), no Helm release to track.
Troubleshooting
Success signal. Open the environment detail view. A healthy install shows the namespace plus the controller pod running and ready and the controller connected. That pair is your green light.
A runtime is "ready" on the instance status, not the parent runtime record, which lags at
requested. Inspect the instance or the pod. → Runtimes
| Symptom | Likely cause | Check |
|---|---|---|
curl: (22) … 401 and kubectl: no objects passed to apply | The install code expired (15 minutes) or was already used. Nothing was changed. | Generate a new command and run it promptly |
Controller pod CrashLoopBackOff or never ready | Invalid controller token, or PlatformSmith unreachable | kubectl -n <env>-system logs deploy/ps-controller; verify egress 443 |
| Pod ready but controller not connected | Egress blocked, or a proxy in the way | Confirm the cluster can reach wss://…/hub/ws on 443 |
| Controller connects but sandboxes never appear | RBAC or namespace mismatch | Confirm both namespaces exist and the RoleBinding targets <env>-sandboxes; check controller logs for RBAC denials |
Sandbox pod stuck Pending | The PVC cannot bind | Verify a default StorageClass exists and can provision |
Sandbox pod ImagePullBackOff | Registry egress blocked | The controller and sandbox images are public and need no pull secret — confirm the cluster can reach the registry |
A launch fails mentioning violates PodSecurity | The pod spec does not meet the namespace's enforced level | Note which pod kind and requirement failed and contact support rather than editing the rendered objects |
A runtime never registers; the log shows runtime registration refused | The connection presented no valid credential | kubectl -n <env>-system logs deploy/ps-controller — the WebSocket closes with code 4401 and a reason code. The credential itself is never logged |
If the environment never reaches "controller connected", verify egress 443 first. Zero-inbound means the only thing that has to work is the controller dialling out.