Executive summary

I read through the vitekube repo, a community project (not an official part of Vitest/Vite/Kubernetes) that injects k8s, namespace, scope, and evidence fixtures into regular Vitest tests so they can run against a real k3d cluster. What stood out to me: the cluster is kept alive between runs, and its build system only re-runs the image/stage whose inputs changed, so reruns can finish in seconds.

The problem it solves

Until now, testing Kubernetes has been slow and painful to debug: a new cluster for every run, rebuilding the whole system for a tiny change, re-running every test just to check one case, and once cleanup kicks in, the trail of a failure (events, logs, pod state) is already gone. Vitekube attacks all of those slow points at once: the cluster can be reused via the reuse: true option, each test gets its own namespace that's automatically deleted when it's done, and evidence (events, pod state, logs) is captured before cleanup and attached directly to the failure output.

Key features and code examples

At its core it's just a regular test from Vitest, equipped with k8s, namespace, scope, and evidence fixtures. Example from the docs:

import { test } from "vitekube";
import { expect } from "vitest";

test("the web server answers", async ({ k8s, namespace }) => {
  await k8s.applyFile(namespace, "web.yaml");
  await k8s.waitForRollout({ namespace, name: "web" }, { timeoutMs: 120_000 });

  await using web = await k8s.portForward({
    namespace,
    resource: "service/web",
    remotePort: 80,
  });

  expect((await fetch(web.url)).status).toBe(200);
});

What I liked: k8s.get distinguishes "the resource genuinely doesn't exist" from a connection/permission error (it only returns null if the API server confirms it doesn't exist), and wait functions like waitForJobCompletion throw immediately as soon as a Job fails, instead of waiting out the full timeout.

It can also be wrapped into its own fixture via .extend, so tests read like product behavior rather than k8s boilerplate:

// fixtures.ts
import { test as base } from "vitekube";

export const test = base.extend("web", async ({ k8s, namespace }, { onCleanup }) => {
  await k8s.applyFile(namespace, "web.yaml");
  await k8s.waitForRollout({ namespace, name: "web" }, { timeoutMs: 120_000 });
  const forward = await k8s.portForward({ namespace, resource: "service/web", remotePort: 80 });
  onCleanup(() => forward.close());
  return { get: (path: string) => fetch(new URL(path, forward.url)) };
});

For larger systems, defineK8sGlobalSetup (from vitekube/node) accepts images, stages, charts, and dependsOn declarations in one place — so you can use the same production Helm chart for testing. Example log shown: when only one source file changes, the database and migrations stages stay skipped, unchanged, with only the api stage running, because its inputs changed.

For failure simulation there's scaleTemporarily (scales down temporarily, auto-restores even if the test throws or times out), and to keep secrets out of the output there's evidence.markSensitive(token) (redacts the value including its base64/JSON forms) or evidence.restrict() for when a secret risks appearing in an undetected form.

Impact and upgrade notes

Requirements: Docker, k3d, kubectl, Vitest 5, and Node ^22.18, ^24.11, or >=26. Installation is npm install --save-dev vitest vitekube, then register K8sReporter and globalSetup in vitest.config.ts with fileParallelism: false (since cluster-scoped isolation isn't yet guaranteed across parallel files). A few operational env vars worth mentioning: VITEKUBE_REBUILD=1 to force a rebuild of all images/stages, VITEKUBE_CLUSTER=<name> to separate clusters per worktree/agent (two runs sharing a cluster will fail rather than collide), and VITEKUBE_KEEP_ON_FAILURE=1 to hold onto a failed test's namespace for manual inspection. Currently only the k3d provider is supported for managed clusters; an existing cluster can be used via provider: "attached" but is rejected unless it carries a vitekube marker or its context is confirmed via allowUnmarkedContext. Since this is still at an "early" stage and an independent community project, I'll treat it as an experiment for CI/local dev for now rather than a straight replacement for an already-established Kubernetes e2e setup.

---

First announced on Hacker News: Show HN. Read the original source for full details.