# Run Kubernetes (kind or k3s) Inside a Cloud Sandbox

> Yes. A Sandboxer sandbox is a full VM with Docker preinstalled, so kind create cluster gave us a Ready node in about 28 seconds in one run on a default sandbox, and the k3s install script about 13 seconds. Deploy with kubectl, expose the app with port-forward or a NodePort, then sbx host the port for an HTTPS URL.

Source: https://sandboxer.com/guides/run-kubernetes-kind-or-k3s-inside-a-sandbox. Updated 2026-10-27.

**Short answer:** Yes. A Sandboxer sandbox is a full VM with Docker preinstalled, so kind create cluster gave us a Ready node in about 28 seconds in one run on a default sandbox, and the k3s install script about 13 seconds. Deploy with kubectl, expose the app with port-forward or a NodePort, then sbx host the port for an HTTPS URL.

## At a glance

We ran both on `default` sandboxes (4 vCPU, 8 GB, 50 GB disk), one cluster per sandbox. Every time below is a single run on 2026-10-27, not a benchmark.

| | kind | k3s |
| - | - | - |
| What it is | Kubernetes nodes as Docker containers | A single binary that runs as a systemd service |
| Install | One binary (plus `kubectl`) | `curl -sfL https://get.k3s.io \| sh -` |
| Cluster up to a Ready node | 27.5 s with `--wait 120s` | 12.8 s (11.6 s install + 1.2 s to Ready) |
| Version we got | kind v0.33.0, Kubernetes v1.37.0 | v1.36.5+k3s1 |
| nginx rollout | 13.4 s | 10.0 s |
| How to reach a pod | `kubectl port-forward --address 0.0.0.0` | NodePort, or the bundled Traefik ingress on port 80 |
| RAM used with nginx running | About 1 GB of 8 GB | About 1.1 GB of 8 GB |
| Same processes after stop and resume | Yes | Yes |

`default` was enough for a single-node cluster with a few pods. For multi-node kind clusters or heavier workloads, pick `large` (8 vCPU, 16 GB); we didn't test multi-node.

## kind

kind runs each Kubernetes node as a Docker container, so it needs Docker and nothing else. Install a pinned release from the [kind quick start](https://kind.sigs.k8s.io/docs/user/quick-start/) and `kubectl` from the [Kubernetes docs](https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/):

```bash
id=sb_k3v9x2mq    # your sandbox id from `sbx new`

sbx exec $id --timeout 300 "curl -sSLO https://dl.k8s.io/release/\$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl \
  && sudo install -m 0755 kubectl /usr/local/bin/kubectl && kubectl version --client"
# Client Version: v1.37.1

sbx exec $id --timeout 300 "curl -sSLo kind https://kind.sigs.k8s.io/dl/v0.33.0/kind-linux-amd64 \
  && chmod +x kind && sudo mv kind /usr/local/bin/ && kind version"
# kind v0.33.0 go1.26.7 linux/amd64
```

Pin the version. On the day we ran this, `https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64` downloaded `v0.34.0-alpha`, not the latest stable release.

Create the cluster. Pass `--wait`, otherwise `kind create cluster` returns before the node is Ready:

```bash
sbx exec $id --timeout 600 "time kind create cluster --name demo --wait 120s >/dev/null 2>&1; kubectl get nodes"
# NAME                 STATUS   ROLES           AGE   VERSION
# demo-control-plane   Ready    control-plane   21s   v1.37.0
#
# real	0m27.542s
```

Without `--wait`, the first run took 22.8 seconds, including pulling the `kindest/node:v1.37.0` image, and `kubectl get nodes` showed `NotReady` for the next 20 seconds or so.

Deploy nginx and wait for it:

```bash
sbx exec $id "kubectl create deployment web --image=nginx --port=80 && kubectl rollout status deploy/web --timeout=180s && kubectl get pods -o wide"
# deployment.apps/web created
# deployment "web" successfully rolled out
# NAME                   READY   STATUS    RESTARTS   AGE   IP           NODE
# web-7b45cc55bc-jbxhw   1/1     Running   0          14s   10.244.0.2   demo-control-plane
```

### Put it on an HTTPS URL

A kind NodePort listens on the node container's address (`172.18.0.2` here), not on the sandbox itself. kind's docs cover publishing node ports with [`extraPortMappings`](https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings). The quicker path is `kubectl port-forward` bound to `0.0.0.0`, run in the background, then `sbx host`:

```bash
sbx exec $id --detach "kubectl port-forward deploy/web 8080:80 --address 0.0.0.0"
sbx host $id 8080 --public
# {"ok":true,"type":"port.hosted","port":8080,"url":"https://brave-exact-parrot-8080.box.sandboxer.com","access":"public",…}

curl -sS -o /dev/null -w "%{http_code} %{time_total}s\n" https://brave-exact-parrot-8080.box.sandboxer.com
# 200 0.215067s
curl -sS https://brave-exact-parrot-8080.box.sandboxer.com | grep title
# <title>Welcome to nginx!</title>
```

Leave off `--public` to get a private URL with a token instead (see [hosting](/docs/hosting)).

To stop the port-forward later, kill it by the `pid` that `--detach` printed. `pkill -f port-forward` also matches the shell `sbx exec` runs it in, which ended our command with exit 143.

## k3s

k3s needs no Docker. The [k3s quick start](https://docs.k3s.io/quick-start) install script downloads the binary, installs a `k3s` systemd service, enables it and starts it:

```bash
sbx exec $id --timeout 600 "time (curl -sfL https://get.k3s.io | sh - 2>&1 | tail -5); \
  time (until sudo k3s kubectl get nodes 2>/dev/null | grep -q \" Ready\"; do sleep 1; done); sudo k3s kubectl get nodes"
# …
# [INFO]  systemd: Enabling k3s unit
# [INFO]  systemd: Starting k3s
# real	0m11.604s
# real	0m1.217s
# NAME                    STATUS   ROLES    AGE   VERSION
# radiant-civic-saffron   Ready    <none>   1s    v1.36.5+k3s1
```

The node takes the sandbox's subdomain as its name. Its `control-plane` role label appeared a few seconds later. Use `sudo k3s kubectl`, because the kubeconfig at `/etc/rancher/k3s/k3s.yaml` is readable only by root.

Deploy nginx and expose it as a NodePort. A k3s NodePort listens on the sandbox itself, so you can host it directly:

```bash
sbx exec $id "sudo k3s kubectl create deployment web --image=nginx --port=80 \
  && sudo k3s kubectl rollout status deploy/web --timeout=180s"
# deployment.apps/web created
# deployment "web" successfully rolled out

sbx exec $id "sudo k3s kubectl expose deploy web --type=NodePort --port=80 && sudo k3s kubectl get svc web"
# service/web exposed
# NAME   TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
# web    NodePort   10.43.239.246   <none>        80:30572/TCP   0s

sbx host $id 30572 --public
curl -sS -o /dev/null -w "%{http_code} %{time_total}s\n" https://radiant-civic-saffron-30572.box.sandboxer.com
# 200 0.202872s
```

A `curl` to the NodePort in the same command as `expose` returned no response. A few seconds later it returned `200`.

### Or use the bundled Traefik ingress

k3s ships Traefik as its ingress controller, listening on port 80. Add an Ingress and host port 80:

```bash
sbx exec $id "sudo k3s kubectl create ingress web --rule=/*=web:80"
sbx host $id 80 --public
curl -sS https://radiant-civic-saffron-80.box.sandboxer.com | grep title
# <title>Welcome to nginx!</title>
```

In the first 20 seconds, `kubectl get pods -n kube-system` showed `helm-install-traefik` in `Error` with one restart. It moved to `Completed` on its own after the next retry, and Traefik started. Wait for it before you add an Ingress.

## Stop and resume keep the cluster running

A stop saves the VM's memory along with its disk, so `sbx resume` brings back the running cluster rather than rebooting it (see [snapshots](/docs/snapshots)). We stopped and resumed both sandboxes:

| | Before stop | After resume |
| - | - | - |
| kind: `kubectl port-forward` | PID 3325 | PID 3325, URL answered `200` at once |
| kind: node | Ready | `NotReady` for about 20 seconds, then Ready |
| kind: nginx pod | Running, 0 restarts | Running, 0 restarts |
| k3s: `k3s server` | PID 728 | PID 728 |
| k3s: node and pod | Ready, Running | Ready, Running, NodePort URL `200` |

The `sbx resume` command took 3.7 s for the kind sandbox and 1.4 s for k3s (single runs, 2026-10-27).

Memory is restored only on a machine with the same CPU type as the one the sandbox stopped on. If none is free within 2 minutes, the resume boots from disk instead: your files and pulled images survive, but running processes don't, and the sandbox shows `resumeReason: "cpu_mismatch"`. Pass `sbx resume --require-memory` to wait for a matching machine instead. A size change (`sbx resume --type large`) always boots from disk. After a boot from disk, k3s starts again because its installer enables the systemd unit. We didn't test whether a kind cluster comes back after a boot from disk, and you would need to start a port-forward again.

## FAQ

### Do I need a large sandbox to run Kubernetes?

No. A single-node kind or k3s cluster with nginx used about 1 GB of the 8 GB on a `default` sandbox. Pick `large` for multi-node kind clusters or many pods.

### Does kind need privileged containers or the host's Docker socket?

No host socket is involved. Each sandbox is its own VM with its own Docker daemon, and kind's privileged node containers run inside that VM, not on a shared host.

### Can I run kind and k3s in the same sandbox?

We ran them in separate sandboxes. Both want the sandbox's ports and CPU, so one cluster per sandbox is the simpler setup.

### How do I share the app with someone?

Run `sbx host` on the port and send the URL. Without `--public`, the URL carries a token and only people with the full link can open it.

### Does the cluster keep running if my laptop disconnects?

Yes. The cluster runs in the sandbox, not on your machine. It keeps running until the sandbox stops: when you run `sbx stop`, at auto-stop, or at an idle pause. A stop keeps the cluster's memory.

## Related

- [Docker and nested KVM inside an agent sandbox](/guides/docker-and-kvm-inside-an-agent-sandbox)
- [Run Docker Compose in a sandbox with a URL per service](/guides/run-docker-compose-in-a-sandbox-with-a-url-per-service)
- [Give a sandbox port an HTTPS URL](/guides/give-a-sandbox-port-an-https-url)
- [Snapshots](/docs/snapshots)
