AI Native WorkshopGo from AI experimentation to AI-native execution across your organization.
← Articles/No. 582 · Kubernetes

How to Deploy Sandbox Environments on Kubernetes: 5 Approaches, Ranked by Isolation and Cost

A practitioner's guide to deploying sandbox environments on Kubernetes: namespace-per-sandbox, GitOps per-PR environments, virtual clusters, ephemeral clusters, and hardened runtimes for untrusted AI agent code - with comparison tables of isolation boundary, creation time, cost driver, and cleanup for each.

Romaric Philogene
CEO & Co-founder
AUG 25, 2026 · 13 MIN
How to Deploy Sandbox Environments on Kubernetes: 5 Approaches, Ranked by Isolation and Cost

Key points:

  • There are five practical ways to deploy a sandbox environment on Kubernetes, in rising order of isolation and cost: namespace-per-sandbox (created in seconds, weakest isolation), GitOps per-PR environments via the Argo CD ApplicationSet pull request generator, virtual clusters such as vCluster (own API server, shared nodes), ephemeral real clusters via Terraform, Pulumi, or kind/k3d in CI, and pod-level sandboxes on a hardened runtime like gVisor or Kata Containers.
  • The minimum viable Kubernetes sandbox is six objects: a Namespace labelled with owner and TTL, a ResourceQuota, a LimitRange, a default-deny ingress and egress NetworkPolicy with a DNS allow-list, the pod-security.kubernetes.io/enforce=restricted label, and a TTL reaper that deletes the namespace when the pull request closes. You can ship all six in an afternoon with kubectl plus Helm or Kustomize.
  • Choose the boundary by the trust level of the code, not by convenience. Trusted internal code is fine in a hardened namespace. Untrusted or LLM-generated code needs a kernel boundary: a RuntimeClass backed by gVisor or Kata Containers, or a separate cluster.
  • A Kubernetes namespace is not a security boundary. Pods in different namespaces share the node kernel, and all pod-to-pod traffic is allowed until a NetworkPolicy selects the pod (Kubernetes docs).
  • For AI agent code execution, Agent Sandbox is a young Kubernetes SIG Apps project (github.com/kubernetes-sigs/agent-sandbox) that ships a Sandbox custom resource for fast-starting, pausable, isolated sandboxes and delegates the actual isolation to gVisor or Kata Containers. It solves a different problem than preview environments.
  • Sandboxes are cheap to create and expensive to forget: the cost is TTL and auto-stop, not compute. If you want a full application environment per pull request without maintaining namespace templating, wildcard DNS, TLS renewal, database seeding, and cleanup controllers, an internal developer platform is the shortest path. Qovery does this inside your own AWS, GCP, Azure, or Scaleway account, or on your existing Kubernetes cluster, with auto-stop on idle.

What is a sandbox environment on Kubernetes, and what are you actually isolating?

A Kubernetes sandbox environment is a short-lived, isolated slice of compute where code runs without touching production, and there are exactly four boundaries you can choose from: namespace (shared control plane and shared kernel), virtual cluster (own API server, shared nodes), separate cluster or dedicated node pool, and sandboxed container runtime (kernel boundary). Everything else in this article is a consequence of that one choice.

Qovery · Agentic Infrastructure Platform
A control plane for platform teams and their coding agents
Learn more

Before anything else, split the word "sandbox," because it means two different things and every other article conflates them. An application sandbox is a preview or test environment per branch or pull request: your app, its dependencies, a URL a reviewer can click. A code-execution sandbox is a place to run untrusted or AI-agent-generated code safely. They look similar on a slide and they need completely different tools. I will keep them split for the rest of this piece.

The three real use cases fall out of that split: developer and PR previews (application sandbox), CI and integration tests (application sandbox), and untrusted code execution for AI agents, user-submitted code, or sales demos (code-execution sandbox).

Here is the isolation ladder, weakest to strongest:

  1. Namespace - shared control plane, shared node kernel.
  2. Virtual cluster - its own API server and CRDs, workloads still land on shared nodes.
  3. Separate cluster or dedicated node pool - separate control plane or separate machines.
  4. Sandboxed container runtime - a kernel boundary per pod via gVisor or Kata Containers.

Now the opinion I will defend for the rest of the article: a Kubernetes namespace is not a security boundary, and treating it like one is how untrusted code turns into a cloud incident. Two facts back this up. First, Kubernetes networking is allow-by-default: all pod-to-pod traffic, ingress and egress, is permitted until a NetworkPolicy selects the pod, and policies are additive and opt-in. Second, a namespace is a scope for names, not a kernel boundary: pods in different namespaces run on the same node and share that node's Linux kernel.

A namespace does not isolate: the node kernel, node resources unless you set a ResourceQuota and LimitRange, cluster-scoped objects (CRDs, ClusterRoles, validating and mutating admission webhooks), or network egress, including the cloud instance metadata endpoint at 169.254.169.254.

The primitives you will use, each linked once so you never have to guess the canonical page: Namespace, ResourceQuota, LimitRange, NetworkPolicy, RBAC, ServiceAccount token automount, Pod Security Admission with its privileged, baseline, and restricted levels, RuntimeClass, taints and tolerations, and finalizers for cleanup.

This is the default question, not a niche one. In the CNCF 2025 Annual Cloud Native Survey, published January 2026, 82% of surveyed container users run Kubernetes in production. If your team runs on Kubernetes, sandboxing on Kubernetes is where this lands.

One line to carry with you: sandboxes are cheap to create and expensive to forget.

How do you deploy a sandbox environment on Kubernetes step by step?

For trusted internal code, deploying a sandbox environment on Kubernetes takes seven steps: create a labelled Namespace, apply a ResourceQuota and LimitRange, apply a default-deny ingress and egress NetworkPolicy with a kube-dns allow-list, set pod-security.kubernetes.io/enforce=restricted, deploy the app as a per-sandbox Helm release or Kustomize overlay, expose it on wildcard DNS with an Ingress or Gateway API route and a wildcard TLS certificate, then register a TTL that deletes it.

The seven steps, with the exact object names and label keys so each is actionable:

  1. Create a Namespace labelled owner, ttl, git-branch, and pr-number. Those labels are what your cleanup and cost tooling will key on later.
  2. Apply a ResourceQuota and a LimitRange so one runaway sandbox cannot starve the cluster.
  3. Apply a default-deny NetworkPolicy for both ingress and egress, then add back only what the app needs, starting with kube-dns on port 53.
  4. Set pod-security.kubernetes.io/enforce=restricted on the namespace.
  5. Deploy the app as a per-sandbox Helm release or Kustomize overlay with a generated release name.
  6. Expose it on wildcard DNS with an Ingress or Gateway API route plus a wildcard or per-environment TLS certificate.
  7. Register a TTL so the namespace is deleted when the pull request closes.

Here is the Namespace, ResourceQuota, and default-deny NetworkPolicy in one manifest:

YAML
apiVersion: v1
kind: Namespace
metadata:
  name: pr-1234
  labels:
    owner: team-checkout
    git-branch: feat-new-cart
    pr-number: "1234"
    ttl: "72h"
    pod-security.kubernetes.io/enforce: restricted
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: sandbox-quota
  namespace: pr-1234
spec:
  hard:
    requests.cpu: "2"
    requests.memory: 4Gi
    limits.cpu: "4"
    limits.memory: 8Gi
    pods: "20"
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
  namespace: pr-1234
spec:
  podSelector: {}          # selects every pod in the namespace
  policyTypes: ["Ingress", "Egress"]
  # no ingress/egress rules = deny all; add a kube-dns allow rule next

And the one command that deploys the app into it:

BASH
helm upgrade --install pr-1234 ./chart \
  --namespace pr-1234 --create-namespace \
  --set image.tag="$GIT_SHA" \
  --set ingress.host="pr-1234.preview.example.com"

The restricted Pod Security Admission level is doing real work here, and most people paste the label without knowing what it enforces. Per the Pod Security Standards, restricted requires runAsNonRoot: true, a seccomp profile of RuntimeDefault, allowPrivilegeEscalation: false, and dropping ALL capabilities (only NET_BIND_SERVICE may be added back). That single label removes a large class of container-escape footguns for free.

Watch the ResourceQuota gotcha. Per the ResourceQuota docs, once a namespace has a CPU or memory quota, every new pod must specify requests or limits for that resource or the control plane can reject it. That is exactly why the LimitRange matters: it supplies defaults so developers do not hand-write requests on every pod just to get scheduled.

DNS and TLS are the underestimated part. You need one wildcard DNS record (*.preview.example.com), a wildcard or per-environment certificate from cert-manager, and an eye on ACME rate limits. Let's Encrypt currently allows up to 50 certificates per registered domain every 7 days and up to 5 certificates per exact set of identifiers every 7 days. Spin up dozens of uniquely named preview environments a day and you will hit those limits, which is the argument for a single wildcard certificate over a fresh cert per sandbox.

Data seeding is where sandboxes get slow or flaky. Three options, with the honest tradeoff each time: restore from a snapshot (accurate, slow), load shared read-only fixtures (fast, limited realism), or use an anonymized production dump (safest realistic data, more work to build). Do not point every sandbox at one shared database. The moment two pull requests write to the same rows, you get cross-talk and flaky tests, and nobody can tell whether the failure is the code or the neighbor.

Treat cleanup as step seven, not as a someday. Three patterns that work: a label-based TTL reaper running as a CronJob that deletes namespaces past their ttl, the Argo CD ApplicationSet pull request generator (which removes the generated Application automatically once the pull request no longer matches, meaning it is closed or merged), or a GitHub Actions job on the pull_request closed event. Whatever you pick, it also has to sweep the orphans a namespace delete leaves behind: PersistentVolumes, cloud load balancers, DNS records, and container images.

Be honest about the ceiling of this approach. A namespace gives you no CRD or cluster-scoped isolation, a shared node kernel, and collisions the moment two sandboxes install an operator or admission webhook that assumes cluster-wide ownership. That is when you climb the ladder.

When should you use a virtual cluster or an ephemeral real cluster instead of a namespace?

Use a virtual cluster such as vCluster when sandboxes need their own API server, CRDs, or cluster-scoped resources but you do not want to pay for a real control plane, and use a full ephemeral cluster only when you must validate node-level, CNI, operator, or Kubernetes upgrade behaviour - which is a small minority of sandboxes.

vCluster from Loft Labs runs a virtual control plane (its own API server, controller manager, and data store) inside a namespace of a host cluster, while a syncer schedules the actual workloads onto the host cluster's nodes. Each tenant gets its own API server, CRDs, and RBAC without operating a real control plane. It is the right tool when platform teams need to test operators and CRDs, or hand a team cluster-admin without handing them your production cluster. Loft describes vclusters as starting in seconds; I would confirm any density or startup number against their docs rather than quoting a figure that moves between releases.

Ephemeral real clusters come in two flavors. Managed EKS, GKE, or AKS clusters created per run with Terraform, Pulumi, or Crossplane give you a genuinely separate control plane. kind or k3d inside a CI runner give you a throwaway cluster at near-zero cloud cost, which is perfect for integration tests that need real cluster behaviour but not real cloud infrastructure. For managed clusters, the cost driver is control-plane hours plus node warm-up, not the workload itself.

That control-plane cost is real and easy to underestimate. As of publication, at list price: Amazon EKS charges $0.10 per cluster per hour, GKE charges a $0.10 per cluster per hour management fee (with a monthly credit that effectively covers one zonal or Autopilot cluster per billing account), and AKS offers a Free control-plane tier and a Standard tier at $0.10 per cluster per hour for the uptime SLA. Work one example: 20 ephemeral clusters, 8 hours a day, 20 working days a month, at $0.10 per hour, is 20 x 8 x 20 x $0.10 = $320 a month in control-plane fees alone, before a single node, load balancer, or NAT gateway.

On creation latency, use the ordering rather than an invented number: a namespace comes up in seconds, a vCluster in tens of seconds, kind or k3d in about a minute, and a managed cloud cluster in many minutes. Seconds versus minutes is the decision, and it usually decides itself.

For running the test suites against these sandboxes, Testkube is the common CI-on-Kubernetes pattern: it runs your tests as native workloads inside the cluster rather than shelling out from an external runner.

The per-sandbox line items teams forget, each with its price page: the control-plane fee, one load balancer and public IP per environment (an AWS Application Load Balancer starts around $0.0225 per hour plus capacity units), a NAT gateway at roughly $0.045 per hour plus $0.045 per GB processed in US East, orphaned EBS or Persistent Disk volumes, and registry egress. None of these show up in a "how much does a pod cost" estimate, and together they are usually larger than the compute.

How do you sandbox untrusted or AI-agent-generated code on Kubernetes?

Untrusted code needs a kernel boundary, and on Kubernetes that means a RuntimeClass backed by gVisor or Kata Containers on a dedicated, tainted node pool, with default-deny egress and the instance metadata endpoint blocked. For AI agent workloads specifically, the Kubernetes SIG Agent Sandbox project provides a Sandbox custom resource built for exactly this shape of work.

Start with the runtime layer. gVisor runs a user-space kernel called the Sentry that intercepts application syscalls, which is the mechanism behind GKE Sandbox. Kata Containers takes a different route and runs each pod inside a lightweight virtual machine. Both are selected per pod through RuntimeClass. Be honest about the cost: syscall interception adds runtime overhead and some syscalls are unimplemented or partial, so test your workload before you commit. A kernel boundary is not free, but for untrusted code it is the point.

The pod-hardening checklist for untrusted workloads:

  • Read-only root filesystem.
  • Drop ALL Linux capabilities.
  • Run as non-root.
  • seccomp profile RuntimeDefault.
  • No hostNetwork and no hostPath volumes.
  • automountServiceAccountToken: false so a compromised pod does not get a free API token.
  • Hard CPU and memory limits.
  • A pod-level timeout via activeDeadlineSeconds.

Egress is the real attack surface, not the container image. Default-deny egress, put an allow-list forward proxy in front of the few domains the agent legitimately needs, and explicitly block 169.254.169.254 so a compromised sandbox cannot reach the cloud metadata service and mint node credentials. On AWS, enforce IMDSv2 (session tokens, and a hop limit that stops pods from reaching the host's metadata) and on GCP the metadata server requires the Metadata-Flavor: Google header, but neither replaces blocking the endpoint at the network layer for untrusted pods.

For AI agents specifically, look at Agent Sandbox (github.com/kubernetes-sigs/agent-sandbox), a Kubernetes SIG Apps project. Its core custom resource is Sandbox (with optional SandboxTemplate, SandboxClaim, and SandboxWarmPool extensions for reusable templates and pre-warmed pools), and it gives sandboxes a stable network identity, persistent storage that survives restarts, and lifecycle management including pausing and resuming. Its stated use cases are AI agent runtimes, executing untrusted or LLM-generated code, development environments, and reinforcement-learning evaluation loops. Two honest notes: it is a young, pre-1.0 project (the latest release is v0.5.6 as of publication, with the API having recently moved from v1alpha1 to v1beta1), so verify the API surface against the repo before you build on it. And it is an orchestrator, not an isolation mechanism: by its own README it delegates the low-level isolation to gVisor or Kata Containers via RuntimeClass.

If you would rather buy than build, the managed code-execution services are a fair option and each has a focus: E2B and Daytona target agent and dev sandboxes, Modal targets serverless code and GPU execution, and Northflank runs both apps and untrusted workloads. Buying beats building when volume is low, you have no platform team, or you have a hard deadline on time-to-first-sandbox.

One boundary stated plainly: Qovery is an internal developer platform for application environments, not an untrusted code-execution sandbox. For agent code, reach for Agent Sandbox, gVisor, or Kata Containers.

If you only do three things for untrusted code: run it on a hardened runtime on a tainted node pool, default-deny egress with no metadata access, and set hard resource limits plus a TTL.

Ship faster on infrastructure you control.
Qovery gives your team a preview environment per pull request on your own AWS, GCP, Azure, or Scaleway account - or your existing Kubernetes cluster - with auto-stop on idle. Start deploying in under 10 minutes.

Which Kubernetes sandbox approach should you choose?

Choose namespace-per-sandbox for trusted application previews, the Argo CD ApplicationSet pull request generator if you already run GitOps, vCluster when sandboxes need their own CRDs, ephemeral clusters for infrastructure-level validation, gVisor, Kata Containers, or Agent Sandbox for untrusted code, and an internal developer platform when you want developers to get environments without learning any of the above.

ApproachIsolation boundaryTypical creation timeCost driver per sandboxCleanup mechanismWho operates itBest forMain limitation
Namespace per sandbox (kubectl + Helm/Kustomize)Namespace only: shared control plane, shared node kernelSecondsPod compute plus one load balancer and DNS record per environmentLabel-based TTL reaper (CronJob) that deletes the namespaceYour platform team on an existing clusterTrusted internal PR and branch previewsNot a security boundary; no CRD or cluster-scoped isolation
Argo CD ApplicationSet pull request generatorNamespace per PR, driven by GitOpsSeconds to a minute after syncPod compute plus per-PR load balancer and DNSApplication auto-removed when the PR closes or no longer matchesYour platform team, already running Argo CDTeams already doing GitOps who want per-PR previews for freeInherits namespace isolation limits; needs Argo CD in place
vCluster (Loft Labs)Virtual control plane: own API server, CRDs, RBAC; workloads on shared host nodesTens of secondsHost-node compute plus vCluster control-plane podsDelete the vCluster (and its host namespace)Your platform team on a host clusterOperator, CRD, and multi-tenant testing without a real control planeShared host kernel; not a hardware or kernel boundary
Ephemeral managed cluster (Terraform / Pulumi / Crossplane)Separate real control plane and nodesMany minutesControl-plane fee (about $0.10/cluster/hour at list price) plus nodes, LB, NATterraform destroy or controller reconcile on teardownYour platform or CI teamKubernetes upgrade, CNI, and node-level validationSlow to create; most expensive per sandbox
kind or k3d in CISeparate throwaway cluster inside the CI runnerAbout a minuteCI runner minutes only; near-zero cloud costRunner is discarded at job endYour CI pipelineIntegration tests needing real cluster behaviour, cheaplyEphemeral and local to the runner; no external URL, limited scale
gVisor or Kata Containers via RuntimeClassKernel boundary per pod (user-space kernel or lightweight VM)Seconds, on a prepared node poolCompute plus runtime overhead on a dedicated tainted node poolDelete the pod; TTL on the workloadYour platform team, node pool pre-configuredRunning untrusted or third-party code with a real kernel boundarySyscall overhead and some unsupported syscalls; node pool setup required
Agent Sandbox (Kubernetes SIG)Orchestrates isolated, stateful pods; delegates kernel isolation to gVisor or KataSeconds, faster from a warm poolCompute plus persistent storage per sandboxController lifecycle management and scheduled deletionYour platform team; you run the controllerAI agent code execution and RL evaluation loopsYoung, pre-1.0 project; you still supply the runtime and operate it
Internal developer platform (Qovery, Northflank)Namespace or environment per PR inside your own cloud account or clusterMinutes, self-serviceUnderlying cloud resources in your account; the platform automates themBuilt-in environment auto-stop and TTL cleanupThe vendor's control plane; workloads run in your infrastructureSelf-service application environments per PR without building toolingNot an untrusted-code sandbox; adds a platform layer to learn

A one-sentence verdict per use case:

  • PR previews: namespace-per-sandbox, or the Argo CD ApplicationSet pull request generator if you already run GitOps.
  • CI integration tests: kind or k3d in the runner for speed and near-zero cloud cost.
  • Operator and CRD testing: vCluster, so each sandbox gets its own API server and CRDs.
  • Kubernetes upgrade testing: an ephemeral managed cluster, because that is the only boundary that actually exercises the control plane and nodes.
  • Untrusted agent code: gVisor or Kata Containers via RuntimeClass, with Agent Sandbox on top if you want the orchestration.
  • Self-service environments for product teams: an internal developer platform, so developers get environments without learning any of the above.

Now match the boundary to the trust level of the code:

Trust levelRequired isolation boundaryMinimum controls
Trusted internal codeHardened namespaceResourceQuota, LimitRange, restricted Pod Security Admission, TTL label
Semi-trusted (third-party dependencies, customer-supplied config)Namespace plus stricter policyEverything above, plus default-deny ingress and egress NetworkPolicy and no metadata access
Untrusted or LLM-generated codeKernel boundary or separate clusterRuntimeClass (gVisor or Kata) on a tainted node pool, default-deny egress with 169.254.169.254 blocked, hard limits, activeDeadlineSeconds

The decision rule in one line: pick the cheapest boundary that matches the trust level of the code, then add a TTL.

How do you stop sandbox environments from blowing up your cloud bill?

Sandbox sprawl, not sandbox creation, is what costs money, so give every sandbox four things it cannot opt out of: an owner label enforced at admission, a hard ResourceQuota, auto-stop on idle, and a TTL that deletes it without a human deciding to.

The four enforced controls:

  • Owner and TTL labels enforced at admission with Kyverno or a Kubernetes ValidatingAdmissionPolicy, so an untagged sandbox simply cannot be created. If you cannot tell who owns it, you will never delete it.
  • A ResourceQuota and LimitRange per namespace, so a single sandbox cannot consume the cluster.
  • Auto-stop on idle, including nights and weekends, because most sandboxes sit doing nothing for the majority of their life.
  • TTL deletion plus an orphan sweep for volumes, load balancers, DNS records, and images.

Right-size aggressively. Sandboxes almost never need production replica counts, HPA minimums, or production instance types. Set requests without inflated limits, run them on a shared node pool with cluster autoscaler or Karpenter for scale-down and consolidation, and put them on interruptible capacity. As of publication, at list price, spot and preemptible instances run up to 90% off on-demand on AWS, up to 91% on GCP, and up to 90% on Azure. Non-production sandboxes are the textbook workload for it.

The waste is measurable. The CAST AI 2026 State of Kubernetes Resource Optimization Report found average CPU utilization of 8% and memory utilization of 20% across the production clusters it analyzed, so most provisioned capacity is paid for and idle. Zoom out to the whole cloud bill and the Flexera 2025 State of the Cloud Report found 84% of organizations name managing cloud spend as their top cloud challenge, and the FinOps Foundation's State of FinOps 2025 ranks workload optimization and waste reduction as the top practitioner priority. Sandboxes are exactly the kind of low-attention, high-churn workload where that waste accumulates.

The single biggest line item is usually dependencies, not compute. Do not spin up an RDS or Cloud SQL instance per sandbox. Run one shared managed database instance and give each sandbox its own schema or logical database. Same pattern for caches and message brokers. The compute for the app is often the cheapest thing in the environment; the per-sandbox managed data services are what quietly triple the bill.

Make the cost visible. Allocate spend by namespace label so every team sees its own sandbox bill, using OpenCost, the CNCF project for Kubernetes cost allocation, or Kubecost, which builds on it. A bill nobody can attribute is a bill nobody will cut.

The anti-pattern to name and avoid: the "we'll clean it up later" long-lived shared staging namespace. It starts as a convenience, and a year later it is an undocumented, permanent production dependency that everyone is afraid to touch. A sandbox with no TTL is not a sandbox, it is future tech debt.

How does Qovery fit in if you want a sandbox per pull request without building it?

Qovery is worth considering when the sandbox you want is a full application environment per pull request and you would rather not maintain namespace templating, wildcard DNS, TLS renewal, database seeding, quota policy, and cleanup controllers yourself. It runs inside your own cloud account or your existing Kubernetes cluster, so the cluster, the data, and the cloud bill stay in your name.

The capabilities I will stand behind: a preview and ephemeral environment per pull request, git-push deployments, per-environment RBAC, environment auto-stop for non-production, managed cluster upgrades, and databases backed by managed cloud services. That is the exact list of things you would otherwise wire together by hand from the sections above.

On where it runs, be precise: Qovery is bring-your-own-cloud on AWS, GCP, Azure, Scaleway, or your own existing Kubernetes cluster (self-managed, on-prem, any distribution). Your committed-use discounts and Savings Plans stay in your name because the workloads run in your account. It is multi-cloud, not AWS-only.

Here is the field observation that made me care about this. After talking with hundreds of CTOs, the pattern repeats almost word for word: a team builds namespace-per-PR in about a week, ships it, feels great, and then spends the next two years maintaining DNS, TLS renewal, quotas, data seeding, and cleanup as the app changes underneath it. The build is the easy week. The maintenance is the cost, and it never shows up in the original estimate.

The honest boundary, for the second time: Qovery is not a replacement for Agent Sandbox, gVisor, or Kata Containers when the job is executing untrusted or AI-generated code. That is a code-execution sandbox and a kernel-boundary problem. Qovery is an application-environment platform. Different problem, different tool.

What you keep versus what you stop maintaining:

  • You keep: your cloud account, your Kubernetes cluster, your data, your committed-use discounts.
  • You stop maintaining: namespace templating, wildcard DNS and TLS, quota policy, seeding jobs, TTL reapers, and orphan cleanup.

To be fair about the category, Northflank is a reasonable alternative that also does preview environments and, unlike Qovery, can run untrusted workloads. The concrete difference is the deployment model, which is to say where the workloads actually run: Qovery deploys into your own cloud account or your existing cluster, so the infrastructure stays yours. Pick based on whether owning the underlying cluster and bill matters to you.

How do you deploy a sandbox environment on Kubernetes step by step?

For trusted internal code on Kubernetes, deploy a sandbox in seven steps: create a labelled Namespace, apply a ResourceQuota and LimitRange, apply a default-deny ingress and egress NetworkPolicy with a kube-dns allow-list, set pod-security.kubernetes.io/enforce=restricted, deploy the app as a per-sandbox Helm release or Kustomize overlay, expose it on wildcard DNS with a TLS certificate from cert-manager, and register a TTL that deletes it when the pull request closes. You can build all of it with kubectl plus Helm or Kustomize in an afternoon.

Is a Kubernetes namespace enough isolation for a sandbox environment?

A Kubernetes namespace is enough for trusted internal code and not enough for untrusted code. A namespace is a scope for names, not a security boundary: pods in different namespaces share the node kernel, and all pod-to-pod traffic is allowed until a NetworkPolicy selects the pod. For untrusted or AI-generated code you need a kernel boundary from gVisor or Kata Containers, or a separate cluster.

What is Agent Sandbox in Kubernetes and when should you use it?

Agent Sandbox is a young Kubernetes SIG Apps project (github.com/kubernetes-sigs/agent-sandbox) that ships a Sandbox custom resource for fast-starting, pausable, stateful sandboxes aimed at AI agent code execution. It is an orchestrator that delegates the actual isolation to gVisor or Kata Containers via RuntimeClass. Use it when you are running untrusted or LLM-generated agent code and want a Kubernetes-native way to manage those sandboxes, but treat it as pre-1.0 (the latest release is v0.5.6 as of publication) and verify its API before you build on it.

How is a virtual cluster like vCluster different from a namespace-based sandbox?

A vCluster gives each sandbox its own Kubernetes API server, CRDs, and RBAC, running inside a namespace of a host cluster while syncing workloads onto the host's nodes (vcluster.com/docs). A namespace-based sandbox shares one API server and one set of cluster-scoped objects across all sandboxes. Use vCluster when sandboxes need to install their own operators or CRDs without colliding; use a plain namespace when they do not, because it is cheaper and simpler.

How do you automatically delete or auto-stop Kubernetes sandbox environments?

Automate deletion with a label-based TTL reaper running as a CronJob, the Argo CD ApplicationSet pull request generator (which removes the environment when the PR closes), or a GitHub Actions job on the pull_request closed event. Pair deletion with auto-stop on idle so environments do not run overnight and on weekends, and sweep the orphans a namespace delete leaves behind: PersistentVolumes, load balancers, DNS records, and container images.

How much does it cost to run one sandbox environment per pull request on Kubernetes?

On an existing cluster, one sandbox as a namespace is mostly pod compute plus one load balancer and a DNS record. The costs stack up when each sandbox gets its own cluster or managed database: at list price, EKS and GKE both charge about $0.10 per cluster per hour, and a NAT gateway adds roughly $0.045 per hour plus $0.045 per GB in US East. The real driver is time-to-delete: idle sandboxes with no TTL, not the price of creating them.

Can you run a sandbox environment per pull request on your own existing Kubernetes cluster?

Yes. You can build namespace-per-PR sandboxes on any existing Kubernetes cluster with kubectl, Helm or Kustomize, cert-manager, and a TTL reaper, or drive them through the Argo CD ApplicationSet pull request generator if you already run GitOps. If you would rather not maintain the DNS, TLS, quota, seeding, and cleanup plumbing yourself, Qovery runs preview environments per pull request on your own AWS, GCP, Azure, or Scaleway account, or your existing self-managed Kubernetes cluster, with environment auto-stop on idle.

Romaric Philogene
About the author
Romaric Philogene

Romaric founded Qovery to make Kubernetes accessible to every engineering team. He writes about platform strategy, developer experience, and the future of cloud infrastructure.

Next step

Ship faster on infrastructure you control.

Qovery gives your team a preview environment per pull request on your own AWS, GCP, Azure, or Scaleway account - or your existing Kubernetes cluster - with auto-stop on idle. Start deploying in under 10 minutes.