Ephemeral Environments with vcluster and GitHub Actions Guide
Master ephemeral environments for pull requests using vcluster and GitHub Actions. Learn how to spin up isolated Kubernetes namespaces for faster testing.
If you’ve ever spent hours debugging a "works on my machine" issue that failed in staging, you know the pain of shared dev environments. They are bottlenecks. Someone deletes a configmap, or a migration goes sideways, and suddenly nobody can work.
I started using ephemeral environments to solve this. By spinning up a fresh, isolated Kubernetes cluster for every pull request preview, we eliminate contention. But spinning up a full EKS or GKE cluster for every PR is expensive and slow. That’s where vcluster changes the game.
Why vcluster for PR Previews?
Standard namespaces are fine, but they don't provide true isolation. If you need custom CRDs, different API versions, or cluster-wide permissions, a namespace won't cut it.
vcluster creates a virtual cluster inside your existing Kubernetes cluster. It’s lightweight, fast, and uses a fraction of the resources of a "real" cluster. Since it runs as a pod, you can spin one up in seconds.
The Architecture
We’ll use GitHub Actions to trigger the creation of a vcluster whenever a PR is opened.
- Trigger: PR opened or synchronized.
- Setup: Install
vclusterCLI andhelmin the runner. - Deploy: Create the virtual cluster.
- Sync: Deploy the application manifests into the virtual cluster.
- Teardown: Delete the vcluster when the PR is closed.
Implementation Guide
First, make sure you have the vcluster CLI installed in your runner. I prefer using the official setup action.
YAML# .github/workflows/ephemeral.yml name: PR Ephemeral Environment on: pull_request: types: [opened, synchronize, closed] jobs: vcluster-lifecycle: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Install vcluster run: curl -L -o vcluster "https://github.com/loft-sh/vcluster/releases/latest/download/vcluster-linux-amd64" && sudo install -c -m 0755 vcluster /usr/local/bin - name: Create vcluster if: github.event.action != 'closed' run: | vcluster create pr-${{ github.event.number }} --namespace pr-${{ github.event.number }} vcluster connect pr-${{ github.event.number }} --kubeconfig=./kubeconfig.yaml - name: Deploy App if: github.event.action != 'closed' run: | export KUBECONFIG=./kubeconfig.yaml kubectl apply -f k8s/deployment.yaml - name: Cleanup if: github.event.action == 'closed' run: vcluster delete pr-${{ github.event.number }}
Key Considerations for Cloud-Native Development
When you move to this model, you'll hit a few hurdles. Here is how I handle them:
1. Ingress and DNS
How do you access the app? I use ingress-nginx with a wildcard domain. Each vcluster gets a unique subdomain: pr-123.dev.example.com. You’ll need to configure your Ingress controller to route requests based on the host header.
2. Resource Quotas
Don't let a stray PR consume your entire node pool. Always set resource limits on the namespace where the vcluster runs.
YAMLapiVersion: v1 kind: ResourceQuota metadata: name: vcluster-quota spec: hard: requests.cpu: "2" requests.memory: 4Gi
3. Persistent Storage
If your app needs a database, don't spin up a managed RDS instance for every PR. It’s too expensive. Use a lightweight operator like Postgres Operator inside the vcluster or use a shared, pre-provisioned dev database instance.
The "Hard-Won" Lessons
I’ve seen teams try to implement this and fail because they forgot about clean-up. GitHub Actions are great, but if a workflow fails, the cleanup step might not run.
Pro-tip: Use a Kubernetes "Janitor" or a TTL controller. I use the loft CLI or a simple cron job that deletes any namespace prefixed with pr- that hasn't been updated in 24 hours. Never rely solely on the GitHub Actions lifecycle for infrastructure cleanup.
Final Thoughts
Integrating vcluster into your CI/CD pipeline is the single most effective way to improve developer velocity. It gives your team the freedom to break things without affecting their peers. It’s not just about cool tech; it's about removing the fear of deployment.
Start small. Try it on one microservice. Once you see the feedback loop tighten, you won't ever want to go back to shared staging environments.