The `secure` runtime class runs each pod in its own lightweight virtual machine, with its own kernel. If a process escapes the container it stays inside that VM, unable to reach the host or a neighbouring pod, even through a kernel-level bug. That is a harder boundary than a [user namespace](/docs/hetzner/apalla/workloads/secure/run-in-a-user-namespace), which remaps UIDs but leaves every pod sharing the one host kernel. Use it when you run untrusted code, or cannot afford a kernel escape. ## Where it runs The `secure` runtime is Kata Containers, and Kata boots a real kernel, so it needs bare-metal nodes with hardware virtualization (KVM). Hetzner Cloud VMs do not expose nested virtualization, so `secure` pods will not schedule on them. No bare-metal workers yet? [Add a bare-metal pool](/docs/hetzner/apalla/servers-and-nodes/provision/add-bare-metal-servers) first. You wire up no node selector; the runtime class carries its own, so the scheduler steers `secure` pods onto capable nodes. When none is free the pod sits `Pending`; it never quietly drops back to the shared-kernel runtime. ## Set the runtime class Add `runtimeClassName: secure` to the pod spec; nothing else about the workload changes. ```yaml title="secure-deployment.yaml" apiVersion: apps/v1 kind: Deployment metadata: name: secure-app spec: replicas: 2 selector: matchLabels: app: secure-app template: metadata: labels: app: secure-app spec: runtimeClassName: secure containers: - name: app image: nginx:stable resources: requests: # size for the container; the VM overhead is added on top cpu: "250m" memory: "128Mi" ``` ## Verify the VM boundary Compare the kernel the pod sees with the node's: ```console $ kubectl exec -- uname -r $ kubectl get node -o jsonpath='{.status.nodeInfo.kernelVersion}' ``` The two versions differ: each `secure` pod runs its own guest kernel inside the VM. A standard-runtime pod reports the node's kernel, because it shares it. ## What a drain does to a secure pod A drain forces the pod to reschedule onto another node. This adds latency, since the guest kernel has to boot again before the container starts, which is what makes a `secure` pod slower to return than an ordinary one. How long the drain waits before the node is torn down is set per pool by `spec.topology.workers.machineDeployments[].deletion.nodeDrainTimeoutSeconds`. Set it high enough on the pool running your `secure` pods that they can hand off cleanly instead of being cut off mid-drain. A pool is one `machineDeployments` entry in your `Cluster` resource; see [Node pools and machine deployments](/docs/hetzner/apalla/servers-and-nodes/pools/node-pools-overview) for where to set this field. ## Limits to budget for - **Per-pod VM overhead.** The VM layer costs roughly 250m CPU and 160Mi memory on top of the container's request. - **hostNetwork and privileged are out.** A pod that sets either cannot use the `secure` runtime. - **`standard`** (the default) uses crun, the lightweight OCI runtime. A pod with no `runtimeClassName` lands here. - **`runc`** is the fallback for the rare image that misbehaves on crun. - **`secure`** is Kata Containers, the per-pod VM described on this page. Confidential-computing variants (Intel TDX, AMD SEV-SNP) are not built, because Hetzner's dedicated servers lack those CPU features.