Day 5: CI/CD with vind: The setup-vind GitHub Action


We’ve been running vind locally all week. But one of the most common uses for local Kubernetes is CI/CD, spinning up ephemeral clusters for end-to-end testing.
If you’re currently using setup-kind in your GitHub Actions, you can switch to setup-vind with minimal changes.
Today, let’s build a real CI/CD pipeline using the setup-vind GitHub Action.
With KinD in CI, you get a basic cluster. With vind, you get:
Here’s the simplest possible workflow:
# .github/workflows/e2e.yaml
name: E2E Tests with vind
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create vind cluster
uses: loft-sh/setup-vind@v1
with:
name: e2e-cluster
- name: Verify cluster
run: |
kubectl get nodes
kubectl get namespaces
That’s it. The setup-vind action:
Let’s build a complete pipeline that deploys an app and runs tests. I set up a demo repo at saiyam1814/vind-demo with this workflow and it passed on the first run.
A simple Kubernetes deployment with 3 replicas:
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: vind-demo
spec:
replicas: 3
selector:
matchLabels:
app: vind-demo
template:
metadata:
labels:
app: vind-demo
spec:
containers:
- name: vind-demo
image: nginx:alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: vind-demo
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
selector:
app: vind-demo
# .github/workflows/e2e.yaml
name: E2E Tests with vind
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create vind cluster
uses: loft-sh/setup-vind@v1
with:
name: e2e-cluster
- name: Verify cluster
run: |
echo "=== Nodes ==="
kubectl get nodes -o wide
echo "=== Namespaces ==="
kubectl get namespaces
echo "=== System Pods ==="
kubectl get pods -A
- name: Deploy application
run: |
kubectl apply -f k8s/deployment.yaml
kubectl rollout status deployment/vind-demo --timeout=180s
- name: Verify deployment
run: |
echo "=== Pods ==="
kubectl get pods -o wide
echo "=== Services ==="
kubectl get svc
echo "=== Deployment ==="
kubectl get deployment vind-demo
READY=$(kubectl get deployment vind-demo -o jsonpath='{.status.readyReplicas}')
if [ "$READY" != "3" ]; then
echo "Expected 3 ready replicas, got $READY"
exit 1
fi
echo "All 3 replicas are ready!"
- name: Test service connectivity
run: |
SVC_IP=$(kubectl get svc vind-demo -o jsonpath='{.spec.clusterIP}')
echo "Service ClusterIP: $SVC_IP"
kubectl run curl-test --image=curlimages/curl:latest \
--restart=Never --rm -i --timeout=60s \
-- curl -sf http://$SVC_IP
Here’s what actually happens when this workflow runs on GitHub Actions:
=== Nodes ===
NAME STATUS ROLES AGE VERSION INTERNAL-IP OS-IMAGE CONTAINER-RUNTIME
e2e-cluster Ready control-plane,master 20s v1.35.0 172.18.0.2 Ubuntu 24.04.4 LTS containerd://2.1.6
=== Pods ===
NAME READY STATUS RESTARTS AGE
vind-demo-xxxxxxxxx-xxxxx 1/1 Running 0 4s
vind-demo-xxxxxxxxx-xxxxx 1/1 Running 0 4s
vind-demo-xxxxxxxxx-xxxxx 1/1 Running 0 4s
=== Services ===
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 25s
vind-demo ClusterIP 10.108.7.150 <none> 80/TCP 5s
All 3 replicas are ready!
Service ClusterIP: 10.108.7.150
The entire workflow cluster creation, deployment, verification, and service connectivity test completes in about 90 seconds. You can see the actual passing run in the repo.

After the job completes (pass or fail), setup-vind automatically:
This means you get debugging logs even on failure, no manual cleanup needed.
For multi-node CI clusters or custom settings:
#vcluster.yaml
experimental:
docker:
nodes:
- name: worker-1
- name: worker-2
- uses: loft-sh/setup-vind@v1
with:
config:vcluster.yaml
Need to test multi-cluster scenarios? Create multiple clusters in the same job:
- uses: loft-sh/setup-vind@v1
with:
name: platform
- uses: loft-sh/setup-vind@v1
with:
name: agent
Each call to `setup-vind` creates an independent cluster. The last cluster created will be the active kubectl context.
If you’re currently using setup-kind, here’s the mapping:
Before (setup-kind):
- uses: engineerd/setup-kind@v0.5.0
with:
version: v0.20.0
image: kindest/node:v1.35.0
- name: Load images
run: kind load docker-image my-app:latest
After (setup-vind):
- uses: loft-sh/setup-vind@v1
with:
version: v0.32.1
kubernetes-version: "1.35.0"
# No image loading needed - Docker images are accessible directly
We’ve covered the basics, local clusters, multi-node, external nodes, and CI/CD. Tomorrow, let’s dive into the features that make vind really shine for day-to-day development: sleep/wake for resource management, the registry proxy deep dive, and custom CNI/CSI configurations.
vind is open source: github.com/loft-sh/vind , so do star the repo if you like vind
Deploy your first virtual cluster today.