releases.shpreview

EKS environment factory guide for vCluster

AWS reports in an AWS Architecture Blog case study that Deloitte’s move to a virtual cluster model on Amazon EKS resulted in 89% faster testing environment provisioning. By consolidating dozens of disparate clusters into a single host cluster with over 50 vCluster instances, the case study says Deloitte saved about 500 QA hours per year. This “Environment Factory” pattern allows platform teams to provide isolated, ephemeral Kubernetes environments on demand without the cost or lag of full cluster provisioning.

This post adapts that general architecture with Pulumi to orchestrate Amazon EKS Auto Mode and vCluster.

The problem: environment sprawl and provisioning lag

Traditional development workflows often rely on one full EKS cluster per developer or feature branch. While this provides strong isolation, it introduces major pain points. Provisioning a full cluster can take 15 minutes or more, which slows down CI/CD pipelines. Managing dozens of clusters also leads to high costs and significant operational overhead.

Platform teams need a “soft multi-tenancy” model. This model should feel like a dedicated cluster to the developer but run on shared infrastructure to keep costs low and startup times fast.

Architecture overview: the host and the tenants

The environment factory architecture consists of two main layers.

  1. Host cluster: A single, reliable EKS cluster managed with EKS Auto Mode. This cluster provides the underlying compute, networking, and storage.
  2. Tenant environments: Virtual clusters (vCluster) running as pods within host namespaces.

According to the vCluster architecture, the virtual control plane handles API requests while a syncer maps virtual resources to the host cluster. This separation allows tenants to manage their own CRDs, namespaces, and RBAC while platform teams use quotas, NetworkPolicies, pod security, IAM boundaries, and node isolation controls to protect the host and other tenants.

Implementation: the EKS Auto Mode host

EKS Auto Mode simplifies the host cluster by automating infrastructure management. It handles node provisioning, scaling, and updates based on pod requirements.

The following snippet shows how to define an EKS cluster with Auto Mode enabled using Pulumi.

<span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">awsx</span> <span class="kr">from</span> <span class="s2">"@pulumi/awsx"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">eks</span> <span class="kr">from</span> <span class="s2">"@pulumi/eks"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">k8s</span> <span class="kr">from</span> <span class="s2">"@pulumi/kubernetes"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">SubnetType</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"@pulumi/awsx/ec2"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">clusterName</span> <span class="o">=</span> <span class="s2">"environment-factory"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">vpc</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">awsx</span><span class="p">.</span><span class="nx">ec2</span><span class="p">.</span><span class="nx">Vpc</span><span class="p">(</span><span class="s2">"environment-factory"</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">enableDnsHostnames</span>: <span class="kt">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">cidrBlock</span><span class="o">:</span> <span class="s2">"10.0.0.0/16"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">subnetSpecs</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">type</span><span class="o">:</span> <span class="nx">SubnetType</span><span class="p">.</span><span class="nx">Public</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">tags</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="p">[</span><span class="sb">`kubernetes.io/cluster/</span><span class="si">${</span><span class="nx">clusterName</span><span class="si">}</span><span class="sb">`</span><span class="p">]</span><span class="o">:</span> <span class="s2">"shared"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s2">"kubernetes.io/role/elb"</span><span class="o">:</span> <span class="s2">"1"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">type</span><span class="o">:</span> <span class="nx">SubnetType</span><span class="p">.</span><span class="nx">Private</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">tags</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="p">[</span><span class="sb">`kubernetes.io/cluster/</span><span class="si">${</span><span class="nx">clusterName</span><span class="si">}</span><span class="sb">`</span><span class="p">]</span><span class="o">:</span> <span class="s2">"shared"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s2">"kubernetes.io/role/internal-elb"</span><span class="o">:</span> <span class="s2">"1"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">],</span>
</span></span><span class="line"><span class="cl"> <span class="nx">subnetStrategy</span><span class="o">:</span> <span class="s2">"Auto"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Create an EKS cluster with Auto Mode enabled.
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">hostCluster</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">eks</span><span class="p">.</span><span class="nx">Cluster</span><span class="p">(</span><span class="s2">"host-cluster"</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span>: <span class="kt">clusterName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">authenticationMode</span>: <span class="kt">eks.AuthenticationMode.Api</span><span class="p">,</span> <span class="c1">// Use API authentication mode for EKS access entries.
</span></span></span><span class="line"><span class="cl"> <span class="nx">vpcId</span>: <span class="kt">vpc.vpcId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">publicSubnetIds</span>: <span class="kt">vpc.publicSubnetIds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">privateSubnetIds</span>: <span class="kt">vpc.privateSubnetIds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">autoMode</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">enabled</span>: <span class="kt">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">hostProvider</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">k8s</span><span class="p">.</span><span class="nx">Provider</span><span class="p">(</span><span class="s2">"host-provider"</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">kubeconfig</span>: <span class="kt">hostCluster.kubeconfig</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span>

Implementation: the environment factory

Once the host cluster is ready, we can build the factory that stamps out tenant environments. Each tenant needs a dedicated namespace, resource quotas, and the vCluster itself.

Tenant guardrails

Before installing vCluster, we set up a namespace and resource quotas to ensure one tenant cannot consume all host resources.

<span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">k8s</span> <span class="kr">from</span> <span class="s2">"@pulumi/kubernetes"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Define a tenant namespace on the host cluster.
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">tenantNamespace</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">k8s</span><span class="p">.</span><span class="nx">core</span><span class="p">.</span><span class="nx">v1</span><span class="p">.</span><span class="nx">Namespace</span><span class="p">(</span><span class="s2">"tenant-alpha"</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">metadata</span><span class="o">:</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"tenant-alpha"</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="p">{</span> <span class="nx">provider</span>: <span class="kt">hostProvider</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Apply resource quotas to the tenant namespace.
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">quota</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">k8s</span><span class="p">.</span><span class="nx">core</span><span class="p">.</span><span class="nx">v1</span><span class="p">.</span><span class="nx">ResourceQuota</span><span class="p">(</span><span class="s2">"tenant-quota"</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">metadata</span><span class="o">:</span> <span class="p">{</span> <span class="kr">namespace</span><span class="o">:</span> <span class="nx">tenantNamespace</span><span class="p">.</span><span class="nx">metadata</span><span class="p">.</span><span class="nx">name</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="nx">spec</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">hard</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pods</span><span class="o">:</span> <span class="s2">"20"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s2">"requests.cpu"</span><span class="o">:</span> <span class="s2">"4"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s2">"requests.memory"</span><span class="o">:</span> <span class="s2">"8Gi"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s2">"limits.cpu"</span><span class="o">:</span> <span class="s2">"8"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s2">"limits.memory"</span><span class="o">:</span> <span class="s2">"16Gi"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="p">{</span> <span class="nx">provider</span>: <span class="kt">hostProvider</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Define a Role for the tenant within their namespace.
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">tenantRole</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">k8s</span><span class="p">.</span><span class="nx">rbac</span><span class="p">.</span><span class="nx">v1</span><span class="p">.</span><span class="nx">Role</span><span class="p">(</span><span class="s2">"tenant-role"</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">metadata</span><span class="o">:</span> <span class="p">{</span> <span class="kr">namespace</span><span class="o">:</span> <span class="nx">tenantNamespace</span><span class="p">.</span><span class="nx">metadata</span><span class="p">.</span><span class="nx">name</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="nx">rules</span><span class="o">:</span> <span class="p">[{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">apiGroups</span><span class="o">:</span> <span class="p">[</span><span class="s2">""</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"> <span class="nx">resources</span><span class="o">:</span> <span class="p">[</span><span class="s2">"pods"</span><span class="p">,</span> <span class="s2">"services"</span><span class="p">,</span> <span class="s2">"configmaps"</span><span class="p">,</span> <span class="s2">"secrets"</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"> <span class="nx">verbs</span><span class="o">:</span> <span class="p">[</span><span class="s2">"get"</span><span class="p">,</span> <span class="s2">"list"</span><span class="p">,</span> <span class="s2">"watch"</span><span class="p">,</span> <span class="s2">"create"</span><span class="p">,</span> <span class="s2">"update"</span><span class="p">,</span> <span class="s2">"patch"</span><span class="p">,</span> <span class="s2">"delete"</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"> <span class="p">}],</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="p">{</span> <span class="nx">provider</span>: <span class="kt">hostProvider</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Bind the Role to a tenant user or group.
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">tenantRoleBinding</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">k8s</span><span class="p">.</span><span class="nx">rbac</span><span class="p">.</span><span class="nx">v1</span><span class="p">.</span><span class="nx">RoleBinding</span><span class="p">(</span><span class="s2">"tenant-role-binding"</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">metadata</span><span class="o">:</span> <span class="p">{</span> <span class="kr">namespace</span><span class="o">:</span> <span class="nx">tenantNamespace</span><span class="p">.</span><span class="nx">metadata</span><span class="p">.</span><span class="nx">name</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="nx">subjects</span><span class="o">:</span> <span class="p">[{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">kind</span><span class="o">:</span> <span class="s2">"User"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="c1">// Replace "tenant-user" with the IAM-mapped user or group for this tenant.
</span></span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s2">"tenant-user"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">apiGroup</span><span class="o">:</span> <span class="s2">"rbac.authorization.k8s.io"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">}],</span>
</span></span><span class="line"><span class="cl"> <span class="nx">roleRef</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">kind</span><span class="o">:</span> <span class="s2">"Role"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span>: <span class="kt">tenantRole.metadata.name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">apiGroup</span><span class="o">:</span> <span class="s2">"rbac.authorization.k8s.io"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="p">{</span> <span class="nx">provider</span>: <span class="kt">hostProvider</span> <span class="p">});</span>
</span></span>

For production use, map these Kubernetes identities to IAM principals using EKS Access Entries, with the legacy aws-auth ConfigMap still appearing in older clusters.

Deploying vCluster with Helm

We use the kubernetes.helm.v3.Release resource to install vCluster. This resource provides controlled Helm lifecycle management for the vCluster release. The values block should be adjusted for each tenant profile to control resource synchronization and control plane behavior. Review the vCluster release notes when changing chart versions because values schema and generated secret names can change across releases.

<span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">k8s</span> <span class="kr">from</span> <span class="s2">"@pulumi/kubernetes"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Install vCluster using the Helm Release resource.
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">vcluster</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">k8s</span><span class="p">.</span><span class="nx">helm</span><span class="p">.</span><span class="nx">v3</span><span class="p">.</span><span class="nx">Release</span><span class="p">(</span><span class="s2">"vcluster-alpha"</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s2">"vcluster-alpha"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">chart</span><span class="o">:</span> <span class="s2">"vcluster"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">version</span><span class="o">:</span> <span class="s2">"0.20.0"</span><span class="p">,</span> <span class="c1">// Tested with vCluster 0.20.x; review release notes before changing versions.
</span></span></span><span class="line"><span class="cl"> <span class="nx">repositoryOpts</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">repo</span><span class="o">:</span> <span class="s2">"https://charts.loft.sh"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="kr">namespace</span><span class="o">:</span> <span class="nx">tenantNamespace</span><span class="p">.</span><span class="nx">metadata</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">values</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="c1">// Explicit sync configuration; adjust per tenant profile.
</span></span></span><span class="line"><span class="cl"> <span class="nx">sync</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">toHost</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pods</span><span class="o">:</span> <span class="p">{</span> <span class="nx">enabled</span>: <span class="kt">true</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="p">{</span> <span class="nx">provider</span>: <span class="kt">hostProvider</span> <span class="p">});</span>
</span></span>

Accessing the virtual cluster

The vCluster generates a kubeconfig that allows developers to interact with the virtual API server. We must treat this kubeconfig as a secret, and the endpoint in that kubeconfig must be reachable from the Pulumi runner before using it to create resources inside the virtual cluster.

<span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">pulumi</span> <span class="kr">from</span> <span class="s2">"@pulumi/pulumi"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">k8s</span> <span class="kr">from</span> <span class="s2">"@pulumi/kubernetes"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Retrieve the vCluster kubeconfig from the generated secret.
</span></span></span><span class="line"><span class="cl"><span class="c1">// The vCluster-generated secret can lag behind Helm release readiness on first creation,
</span></span></span><span class="line"><span class="cl"><span class="c1">// so teams may choose an explicit readiness check or rerun after the virtual control plane initializes.
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">vclusterKubeconfig</span> <span class="o">=</span> <span class="nx">k8s</span><span class="p">.</span><span class="nx">core</span><span class="p">.</span><span class="nx">v1</span><span class="p">.</span><span class="nx">Secret</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s2">"vcluster-secret"</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pulumi</span><span class="p">.</span><span class="nx">interpolate</span><span class="sb">`</span><span class="si">${</span><span class="nx">tenantNamespace</span><span class="p">.</span><span class="nx">metadata</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="sb">/vc-vcluster-alpha`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">provider</span>: <span class="kt">hostProvider</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">dependsOn</span><span class="o">:</span> <span class="p">[</span><span class="nx">vcluster</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">).</span><span class="nx">data</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="nx">data</span> <span class="o">=></span> <span class="nx">Buffer</span><span class="p">.</span><span class="kr">from</span><span class="p">(</span><span class="nx">data</span><span class="p">[</span><span class="s2">"config"</span><span class="p">],</span> <span class="s2">"base64"</span><span class="p">).</span><span class="nx">toString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Export the kubeconfig as a secret.
</span></span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">tenantKubeconfig</span> <span class="o">=</span> <span class="nx">pulumi</span><span class="p">.</span><span class="nx">secret</span><span class="p">(</span><span class="nx">vclusterKubeconfig</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Create a provider for the virtual cluster using the secret kubeconfig.
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">vclusterProvider</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">k8s</span><span class="p">.</span><span class="nx">Provider</span><span class="p">(</span><span class="s2">"vcluster-provider"</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">kubeconfig</span>: <span class="kt">tenantKubeconfig</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span>

Operational caveats

  • RBAC and permissions: vCluster generates default RBAC rules that work for most scenarios. However, if your host cluster is heavily locked down, you may need to provide additional permissions to the vCluster service account.
  • Helm release previews: When using kubernetes.helm.v3.Release, Pulumi previews may not show every detail of the rendered Kubernetes resources. It primarily tracks the state of the Helm release itself.
  • EKS Auto Mode node lifetime: EKS Auto Mode uses immutable AMIs and has a 21-day node lifetime. Kubernetes reschedules vCluster pods and tenant workloads when nodes are replaced, so configure replicas, PodDisruptionBudgets, requests, and persistent storage for disruption tolerance.

Conclusion: ephemeral environments at scale

By combining Pulumi with EKS Auto Mode and vCluster, you can build a scalable environment factory. This approach provides the isolation developers need while maintaining the speed and cost-efficiency required by platform teams.

The snippets provided here are adapted for illustration. In a production environment, you would likely wrap these resources into a Pulumi ComponentResource to provide a clean, reusable API for your internal developers. When a feature branch is merged, deleting the Pulumi stack removes the resources managed by that stack, but validate namespace finalizers, persistent volume reclaim policies, and external cloud artifacts as part of cleanup.

For more on managing EKS with Pulumi, see the EKS guide.

Fetched June 4, 2026