Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

RuntimeClass and Sandboxed Containers

By Kokil Thapa | Last reviewed: September 2026

RuntimeClass and Sandboxed Containers solve a problem standard Kubernetes pods cannot: strong isolation between container workloads and the host kernel. Default pods use runc and share the node kernel. A container escape or kernel bug can affect every pod on that node. For multi-tenant clusters, legal-tech portals with document uploads, or any workload running untrusted code, that risk is unacceptable. This guide explains how Kubernetes selects container runtimes through the CRI, how RuntimeClass wires alternate runtimes into scheduling, and what you should configure before putting sandboxed pods into production.

What Are RuntimeClass and Sandboxed Containers in Kubernetes?

RuntimeClass is a Kubernetes API resource introduced in v1.12 and promoted to stable in v1.20. It tells the kubelet which container runtime handler to use when starting a pod. Sandboxed containers run inside an extra isolation layer—a user-space kernel like gVisor, or a lightweight VM like Kata—rather than calling the host kernel directly through runc.

Think of RuntimeClass as a label the scheduler and kubelet understand. You define a handler name that matches what containerd or CRI-O exposes. Pods that reference that RuntimeClass land only on nodes where the handler is installed and working.

RuntimeClass and Sandboxed Containers StackPodruntimeClassNameKubeletCRI callSandboxgVisor or KataHost KernelStandard runc pod (no RuntimeClass)ContainerruncShared KernelHigher isolation boundary with sandbox runtime
RuntimeClass and Sandboxed Containers add an isolation layer between pods and the host kernel compared to default runc.

The three sandbox runtimes you will encounter most often are gVisor, Kata Containers, and Firecracker-backed Kata on AWS. gVisor intercepts syscalls in a user-space kernel called Sentry. Kata spins up a micro-VM per pod or container. Both reduce the attack surface compared to runc, but they add CPU and memory overhead.

On clusters I help maintain for Linux server and deployment work, RuntimeClass rarely appears on day one. Teams add it after their first security review flags shared-kernel risk. That timing is normal. Plan the node pool before you flip production traffic.

Core RuntimeClass fields

A minimal RuntimeClass object names the handler and optionally restricts scheduling:

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor
handler: runsc
scheduling:
  nodeSelector:
    sandbox.gvisor.io/enabled: "true"
  tolerations:
  - key: sandbox.gvisor.io/enabled
    operator: Equal
    value: "true"
    effect: NoSchedule

The handler value must match the runtime handler registered in containerd (/etc/containerd/config.toml) or CRI-O. The optional scheduling block injects nodeSelector and tolerations into pods that use this class. That keeps sandbox pods off generic worker nodes.

How Do You Configure RuntimeClass for gVisor or Kata Containers?

Configuration happens in three layers: install the sandbox runtime on nodes, register a handler with your CRI shim, then create the RuntimeClass and pod specs. Skipping any layer produces pods stuck in ContainerCreating with opaque CRI errors.

Step 1: Register the handler in containerd

For gVisor with containerd, add a runtime handler pointing to runsc:

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
  runtime_type = "io.containerd.runsc.v1"

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc.options]
  TypeUrl = "io.containerd.runsc.v1.options"
  ConfigPath = "/etc/containerd/runsc.toml"

Restart containerd after editing the config. Confirm the handler appears:

ctr plugins ls | grep runsc

Step 2: Label and taint sandbox nodes

Dedicate a node pool for sandbox workloads. Generic Laravel or API pods should stay on standard nodes. Sandbox overhead is real.

  1. Install gVisor or Kata on each sandbox node.
  2. Label nodes: kubectl label node worker-sandbox-1 sandbox.gvisor.io/enabled=true
  3. Add a NoSchedule taint so only tolerant pods schedule there.
  4. Create the RuntimeClass with matching nodeSelector and tolerations.
  5. Reference runtimeClassName: gvisor in pod templates.

Step 3: Assign RuntimeClass to a pod

apiVersion: v1
kind: Pod
metadata:
  name: untrusted-worker
spec:
  runtimeClassName: gvisor
  containers:
  - name: worker
    image: myregistry.io/jobs/processor:1.4.2
    resources:
      requests:
        cpu: "500m"
        memory: "512Mi"
      limits:
        cpu: "1"
        memory: "1Gi"

Deployments, Jobs, and CronJobs accept runtimeClassName under spec.template.spec. The field is immutable on running pods. You must recreate the workload to change runtime class.

RuntimeClass Setup FlowInstallgVisor / KataCRI Handlercontainerd configRuntimeClassAPI objectPod SpecruntimeClassNameNode pool separationStandard nodesrunc — app podsLaravel, APIs, queuesSandbox nodesgVisor / Kata poolUntrusted workloads
Configure RuntimeClass and Sandboxed Containers by wiring CRI handlers, API objects, and dedicated node pools in sequence.

Managed Kubernetes simplifies parts of this. GKE Sandbox uses gVisor through Pod annotations and node auto-provisioning. EKS and self-managed clusters require manual handler setup. Always read your cloud provider notes before assuming RuntimeClass names.

When Should You Use Sandboxed Containers Instead of Standard runc?

Not every pod belongs in a sandbox. Default runc is faster and simpler. Sandboxed containers earn their cost when the blast radius of compromise must shrink.

CriteriaStandard runcSandboxed (gVisor / Kata)
Kernel isolationShared host kernelUser-space kernel or micro-VM boundary
Performance overheadLow — native syscallsModerate to high — syscall interception or VM boot
Startup latencySub-second typicalgVisor: small penalty; Kata: higher cold start
CompatibilityFull Linux syscall surfaceSome syscalls unsupported or emulated
Best fitTrusted internal servicesMulti-tenant, untrusted code, strong compliance
Ops complexityDefault everywhereSeparate node pool, monitoring, tuning

Use sandboxed containers when you run third-party plugins, user-supplied scripts, PDF converters, or CI job runners inside the cluster. Legal-tech portals with client document processing are a concrete example from my work. Untrusted uploads should not share a kernel with your database pod.

Skip sandboxes for latency-sensitive paths. Payment callback handlers, Redis-backed session stores, and high-QPS API gateways usually stay on runc. You can still isolate them with network policies and resource limits.

Compliance teams sometimes mandate sandboxes for PCI or SOC 2 scope reduction. RuntimeClass gives auditors a clear control: this Deployment spec field proves the workload never scheduled without the alternate runtime. Document the RuntimeClass name in your security baseline.

gVisor vs Kata at a glance

  • gVisor — Lower memory footprint per pod. Good density on smaller nodes. Watch for syscall compatibility with glibc, FUSE, and certain Go runtime features.
  • Kata Containers — Hardware-virtualization isolation. Stronger boundary, closer to a tiny VM. Needs nested virt or bare metal. Higher per-pod overhead.
  • Managed wrappers — GKE Sandbox, EKS Fargate (different model), and similar products hide handler wiring but still impose compatibility limits.

Benchmark both on your actual container images before choosing. A WooCommerce plugin stack behaves differently from a slim Alpine job runner. Use the same JSON formatter you use for API debugging to inspect RuntimeClass status returned by kubectl get runtimeclass -o json.

How Do RuntimeClass and Pod Security Work Together?

RuntimeClass is not a replacement for Pod Security Standards, seccomp, or AppArmor. It complements them. PSS restricts capabilities, volumes, and privilege escalation. RuntimeClass changes which kernel answers syscalls. You need both layers for defense in depth.

Enable the RuntimeClass admission plugin if you run a custom API server build. Most distributions enable it by default on supported versions. Without it, RuntimeClass objects exist but scheduling hints may not apply correctly.

Pair sandbox pods with restricted PSS at the namespace level:

apiVersion: v1
kind: Namespace
metadata:
  name: untrusted-jobs
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Mutating admission webhooks can inject runtimeClassName automatically. That prevents developers from forgetting the sandbox on sensitive namespaces. See admission controllers and validating webhooks for the pattern. A validating webhook can reject pods in untrusted-jobs that omit the gVisor RuntimeClass.

Defense in Depth for Sandboxed PodsImage ScanTrivy / CI gatePSSrestricted nsRuntimeClassgVisor / KataNetworkPolicyegress lockUntrusted workload pod (center)Sandbox podruntimeClassName: gvisorno hostPath, no privileged
RuntimeClass and Sandboxed Containers work best alongside image scanning, Pod Security Standards, and network policies.

Scan images before they reach sandbox nodes. A sandbox containing a backdoored image is still a compromised process. It just cannot easily pivot through the kernel. Combine RuntimeClass with container image scanning in CI and a private registry like Harbor.

Rootless containers reduce host compromise risk from privileged daemon accounts. Sandboxes reduce kernel sharing risk inside the pod boundary. They solve different problems. Do not treat either as a full substitute for the other.

What Are Common RuntimeClass and Sandboxed Container Mistakes?

Most production failures trace back to scheduling, compatibility, or missing observability—not the RuntimeClass YAML itself.

Mistake 1: Handler name mismatch

The RuntimeClass handler must exactly match the CRI registration. runsc vs gvisor typos leave pods in ContainerCreating forever. Check kubelet logs:

journalctl -u kubelet -f | grep -i runtime

Mistake 2: Scheduling sandbox pods on generic nodes

Without nodeSelector, tolerations, or dedicated pools, the scheduler places gVisor pods on nodes that lack runsc. Use RuntimeClass scheduling or taints consistently. Mixed pools cause intermittent failures that are painful to debug.

Mistake 3: Assuming full Linux compatibility

gVisor does not implement every syscall. eBPF tooling, certain io_uring paths, and some database engines fail silently or crash on start. Run integration tests on sandbox nodes before cutover. Keep a runc fallback Deployment for workloads that fail compatibility checks.

Mistake 4: Ignoring overhead in capacity planning

Sandbox nodes handle fewer pods per CPU core. Autoscaling rules tuned for runc will under-provision sandbox pools. Add headroom: plan 20–40% extra CPU for gVisor-heavy fleets. Kata often needs more.

Mistake 5: No monitoring of sandbox-specific metrics

Track OOM kills, syscall emulation errors, and pod start latency separately for sandbox node pools. Standard node-exporter dashboards hide sandbox regressions until users complain.

Sandbox Runtime Decision TreeUntrusted code?NoUse runcPSS + limitsYesNeed VM isolation?NogVisor RuntimeClassYesKata RuntimeClassValidate syscall compat and benchmark before production
Use this decision flow to pick RuntimeClass and Sandboxed Containers versus standard runc for each workload type.

For teams shipping enterprise applications on Kubernetes, document which Deployments require sandbox runtimes in your runbook. Onboarding developers should not guess from namespace names alone.

Projects like Adventure Third Pole Trek run trusted Laravel and Livewire code on standard runc nodes. Only auxiliary workers—image thumbnailing, virus scanning hooks, or imported user scripts—belong in a sandbox pool. That split keeps costs predictable.

Key Takeaways

  • RuntimeClass tells Kubernetes which CRI handler—gVisor, Kata, or custom—to use instead of default runc.
  • Install the sandbox runtime on dedicated nodes, register the handler in containerd or CRI-O, then set runtimeClassName on pod specs.
  • Use sandboxed containers for untrusted or multi-tenant workloads; keep latency-critical trusted services on runc.
  • Combine RuntimeClass with Pod Security Standards, network policies, and image scanning—sandboxes are one layer, not the whole stack.
  • Test syscall compatibility and benchmark overhead before migrating production traffic; keep a runc fallback for incompatible images.
  • Monitor sandbox node pools separately and align autoscaling with higher per-pod resource cost.

People Also Ask

Is RuntimeClass enabled by default in Kubernetes?

RuntimeClass is a stable API resource on supported Kubernetes versions. The RuntimeClass admission plugin is enabled on most standard distributions. You still must create RuntimeClass objects and install sandbox handlers yourself. Nothing sandbox-related works out of the box without node-level setup.

Can you use RuntimeClass with Docker Desktop or kind?

Local clusters can run gVisor for testing if you install runsc and patch containerd config manually. kind and minikube support is possible but fiddly. Treat local sandbox clusters as compatibility labs, not performance benchmarks. Production tuning needs real node pools.

Does gVisor replace seccomp and AppArmor profiles?

No. gVisor provides kernel isolation. seccomp and AppArmor restrict which syscalls and paths a container uses inside that boundary. Apply restricted Pod Security Standards and seccomp profiles even on sandbox pods for defense in depth.

How does RuntimeClass differ from Kubernetes PodSecurityPolicy?

PodSecurityPolicy was removed in Kubernetes 1.25. Pod Security Standards replaced it for pod hardening. RuntimeClass selects the container runtime handler. PSS controls privileges and volumes. Use both: PSS for pod spec constraints, RuntimeClass for runtime isolation.

Ship Safer Workloads With the Right Runtime Boundary

RuntimeClass and Sandboxed Containers give you a practical knob for kernel isolation without abandoning Kubernetes. Start with one untrusted workload class—document converters, CI runners, or third-party plugins—and prove compatibility on a dedicated node pool. Expand only after metrics look stable.

If you are planning cluster hardening for a Laravel API, legal-tech portal, or multi-tenant SaaS platform, map trusted and untrusted paths first. Not every pod needs gVisor. The ones that do should be provably sandboxed through RuntimeClass, admission policy, and CI scanning.

Need help designing node pools, CRI config, or deployment pipelines for sandboxed workloads? Review our support and maintenance services or custom software development offerings. For broader container security reading, see scanning container images for vulnerabilities and deploying containers on Amazon ECS with Fargate. Official references: the Kubernetes RuntimeClass documentation, gVisor installation guide, and the RuntimeClass KEP.

Contact us to audit your current cluster layout, or explore more guides on the blog and about page.

Frequently Asked Questions

RuntimeClass is a stable Kubernetes API resource that tells the kubelet which CRI container runtime handler to use when starting a pod, such as runsc for gVisor instead of default runc.

Sandboxed containers run inside an extra isolation layer rather than calling the host kernel directly through runc. The two approaches you will encounter most often are gVisor, which intercepts syscalls in a user-space kernel called Sentry, and Kata Containers, which spins up a micro-VM per pod or container. Both reduce attack surface compared to default pods that share the node kernel, but they add CPU and memory overhead. On clusters I help maintain, teams usually add sandboxing after a security review flags shared-kernel risk, not on day one.

Configuration happens in three layers, and skipping any layer leaves pods stuck in ContainerCreating with opaque CRI errors. First, install the sandbox runtime on dedicated nodes and register a handler in containerd or CRI-O that matches your RuntimeClass handler field exactly. Restart the CRI shim and confirm the handler appears. Second, label and taint sandbox nodes, then create a RuntimeClass with matching nodeSelector and tolerations so sandbox pods never land on generic workers. Third, set runtimeClassName on pod specs or Deployment, Job, and CronJob templates. Managed Kubernetes differs: GKE Sandbox uses gVisor through pod annotations, while EKS and self-managed clusters require manual handler setup.

Default runc is faster and simpler, so not every pod belongs in a sandbox. Use sandboxed containers when the blast radius of compromise must shrink: multi-tenant clusters, third-party plugins, user-supplied scripts, PDF converters, CI job runners, or legal-tech portals processing client document uploads. Untrusted uploads should not share a kernel with your database pod. Skip sandboxes for latency-sensitive paths such as payment callback handlers, Redis-backed session stores, and high-QPS API gateways; network policies and resource limits still isolate those on runc. Compliance teams sometimes mandate sandboxes for PCI or SOC 2 scope reduction because runtimeClassName proves the workload never scheduled without the alternate runtime.

RuntimeClass is a stable API resource on supported Kubernetes versions, and the RuntimeClass admission plugin is enabled on most standard distributions. You still must create RuntimeClass objects and install sandbox handlers on nodes yourself. Nothing sandbox-related works out of the box without that node-level setup.

gVisor offers a lower memory footprint per pod and suits better density on smaller nodes, but watch for syscall compatibility with glibc, FUSE, and certain Go runtime features. Kata Containers provides hardware-virtualization isolation with a stronger boundary closer to a tiny VM, though it needs nested virtualization or bare metal and carries higher per-pod overhead. Managed wrappers like GKE Sandbox hide handler wiring but still impose compatibility limits. Benchmark both on your actual container images before choosing; a WooCommerce plugin stack behaves differently from a slim Alpine job runner. Keep a runc fallback Deployment for workloads that fail compatibility checks on sandbox nodes.

RuntimeClass is not a replacement for Pod Security Standards, seccomp, or AppArmor; it complements them. PSS restricts capabilities, volumes, and privilege escalation, while RuntimeClass changes which kernel answers syscalls. You need both layers for defense in depth. Pair sandbox pods with restricted PSS at the namespace level and consider mutating admission webhooks that inject runtimeClassName automatically so developers cannot forget the sandbox on sensitive namespaces. A validating webhook can reject pods in untrusted namespaces that omit the gVisor RuntimeClass. Also combine RuntimeClass with container image scanning in CI and a private registry. A sandbox containing a backdoored image is still compromised; it just cannot easily pivot through the kernel.

PodSecurityPolicy was removed in Kubernetes 1.25. Pod Security Standards replaced it for pod hardening at the namespace level. RuntimeClass solves a different problem: it selects which container runtime handler the kubelet uses, such as gVisor runsc or Kata, instead of default runc. PSS controls privileges, volume types, and escalation settings inside the pod boundary. RuntimeClass controls kernel isolation outside that boundary. Use both together. Document the RuntimeClass name in your security baseline so auditors see a clear control proving sensitive workloads never scheduled without the alternate runtime. Enable the RuntimeClass admission plugin on custom API server builds; most distributions enable it by default.

Most production failures trace back to scheduling, compatibility, or missing observability, not the RuntimeClass YAML itself. Handler name mismatch between the RuntimeClass object and CRI registration leaves pods in ContainerCreating forever; check kubelet logs for runtime errors. Scheduling sandbox pods on generic nodes without nodeSelector, tolerations, or dedicated pools causes intermittent failures when runsc is missing. Assuming full Linux compatibility breaks workloads because gVisor does not implement every syscall; eBPF tooling, certain io_uring paths, and some database engines fail silently. Ignoring overhead in capacity planning under-provisions sandbox pools. Failing to monitor OOM kills, syscall emulation errors, and pod start latency separately hides regressions until users complain.

Local clusters can run gVisor for testing if you install runsc and patch containerd config manually. kind and minikube support is possible but fiddly. Treat local sandbox clusters as compatibility labs, not performance benchmarks; production tuning needs real node pools with dedicated sandbox nodes, taints, and autoscaling rules aligned to higher per-pod cost.

No. gVisor provides kernel isolation by intercepting syscalls in user space. seccomp and AppArmor restrict which syscalls and paths a container uses inside that boundary. Apply restricted Pod Security Standards and seccomp profiles even on sandbox pods. Rootless containers and sandboxes solve different problems and neither substitutes for the other.

Sandboxed containers carry moderate to high overhead compared to native runc syscalls. gVisor adds a small startup latency penalty; Kata cold starts cost more because each pod boots a micro-VM. Sandbox nodes handle fewer pods per CPU core, so autoscaling rules tuned for runc will under-provision sandbox pools. Plan roughly 20 to 40 percent extra CPU for gVisor-heavy fleets; Kata often needs more headroom. Track resource usage separately on sandbox node pools rather than relying on standard node-exporter dashboards alone, which hide sandbox-specific regressions until latency or OOM complaints surface.

Set spec.runtimeClassName on the pod to the name of your RuntimeClass object, for example gvisor. Deployments, Jobs, and CronJobs accept runtimeClassName under spec.template.spec so every replica inherits the sandbox runtime. The field is immutable on running pods; you must recreate the workload to change runtime class. The RuntimeClass handler value must exactly match what containerd or CRI-O exposes, and optional scheduling blocks on the RuntimeClass inject nodeSelector and tolerations into pods that reference it, keeping them on nodes where the sandbox handler is installed and working.

Sandbox overhead is real, and mixing sandbox pods with generic Laravel or API workloads on the same nodes makes capacity planning unpredictable. Dedicate a node pool, install gVisor or Kata on each sandbox node, label them, and add a NoSchedule taint so only tolerant pods schedule there. Match those labels and tolerations in your RuntimeClass scheduling block. Without this separation, the scheduler places gVisor pods on nodes lacking runsc, producing intermittent ContainerCreating failures that are painful to debug. Trusted internal services stay on standard runc nodes; only auxiliary workers such as image thumbnailing, virus scanning hooks, or imported user scripts belong in the sandbox pool.

Start with handler name mismatch, the most common cause. The RuntimeClass handler must exactly match the CRI registration in containerd config or CRI-O; typos like runsc versus gvisor leave pods pending indefinitely. Confirm the handler appears after restarting containerd. Next verify scheduling: sandbox pods need nodes with the runtime installed, correct labels, and matching tolerations. Check kubelet logs with journalctl for runtime-related errors. If the pod starts then crashes, suspect syscall compatibility; gVisor does not support every Linux syscall, and some database engines or eBPF tooling fail on start. Run integration tests on sandbox nodes before cutover and keep a runc fallback Deployment for incompatible images.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: