GOLDEN IMAGES
Golden Windows images
Kryton ships no Windows media. Operators build sysprepped golden images and register them as CDI DataSource objects in kryton-images. The pipeline below is adapted from WinForge — dockur install → Sysprep → qcow2 capture → CDI bootstrap.
Pipeline overview¶
dockur/windows Sysprep + capture CDI bootstrap
(build-golden-image) → (FINALIZE=1) → (bootstrap-kubevirt-images)
│ │ │
install guest golden.qcow2 DataSource + PVC
Three ways to get the image into the cluster:
| Mode | When to use |
|---|---|
| Upload (default) | You have a local qcow2 on the operator host |
| HTTP import | Image is published to object storage (S3, MinIO, etc.) |
| Manual manifest | GitOps / hand-crafted CDI YAML |
1. Build a golden image (automated)¶
Uses dockur/windows for hands-free ISO download and install, plus Kryton's deploy/dockur/oem/install.bat for Sysprep generalization.
Fully automated (CLI)¶
VERSION=11e ./scripts/build-golden-image.sh --auto
This will:
- Start
dockurr/windowswith the OEM hook mounted - Download and install Windows unattended (watch at
http://<host>:8066/) - Run Sysprep via
install.batwhen setup completes - Capture
out/windows-11e-golden.qcow2automatically
From the Kryton UI¶
Open Images → Golden image factory and click Build golden image. The dashboard shows a 5-step progress bar and embeds the dockur web viewer while Windows installs.
Requires docker + /dev/kvm on the krytond host (capabilities.goldenImages: true).
From Zorvia¶
Zorvia proxies this API under /api/v1/kryton/golden (set KRYTON_URL, KRYTON_TOKEN, KRYTON_PROJECT on the Zorvia server). Its Golden Images page starts builds, shows progress, runs the CDI bootstrap and shows the passport, alongside Zorvia's own Linux cloud-image imports and VM captures. Starting a build needs the Zorvia vm.create permission; the Kryton token never reaches the browser. When goldenImages is false, the page shows the builder as unavailable and passes through Kryton's error.
Manual (interactive)¶
VERSION=11e ./scripts/build-golden-image.sh
# watch http://localhost:8066/ then:
BUILD_ID=... WORKDIR=... VERSION=11e FINALIZE=1 ./scripts/build-golden-image.sh
API¶
curl -X POST http://127.0.0.1:9088/api/v1/golden \
-H 'Content-Type: application/json' \
-d '{"imageId":"windows-11-enterprise","auto":true}'
curl http://127.0.0.1:9088/api/v1/golden
Boot-readiness gate (guestkit)¶
If guestkit is on the build host's PATH,
build-golden-image.sh runs guestkit gate against the captured qcow2 right after
qemu-img convert/check, before the build is marked ready. This flags, it
doesn't block: a low score or a missing binary still lets the build reach
ready — it just records certified/validationScore on the GoldenBuild (and
on the catalog Image once bootstrapped) so operators see boot-readiness before
publishing to KubeVirt, instead of finding out at cutover.
# Install once per build host (Linux amd64):
curl -fsSL -o guestkit.tar.gz \
https://github.com/zyvorai/guestkit/releases/latest/download/guestkit-linux-amd64.tar.gz
tar xzf guestkit.tar.gz && sudo install -m755 guestkit /usr/local/bin/
| Env var | Default | Meaning |
|---|---|---|
GUESTKIT_BIN |
guestkit |
Binary name/path to invoke |
GUESTKIT_MIN_SCORE |
70 |
--fail-below passed to guestkit gate |
SKIP_GUESTKIT |
0 |
Set 1 to skip the gate entirely on hosts without guestkit |
guestkit gate needs root to mount the disk via NBD/loop, so the FINALIZE step
runs it under sudo — make sure the build-host user has passwordless sudo (or
run the whole script as root, consistent with scripts/e2e-kryton-golden-host.sh's
existing host-level access assumption). krytonctl doctor reports whether
guestkit is installed (informational, not a failure).
Right after the gate, the same guestkit binary emits a full Cutover
Passport (guestkit passport emit) — the
evidence behind the score: BitLocker/VirtIO/activation/hotfix flags, critical
blockers, and a fix plan. It's written next to the qcow2
(<qcow2>.passport.json), recorded as GoldenBuild.passportPath, and served
raw at GET /api/v1/golden/{id}/passport (404 if guestkit wasn't available at
build time). The Images page's golden-image panel links to it as View
passport once a build reaches ready, alongside a Certified/score badge —
and stored golden images show the same Certified badge on their catalog card.
Gotchas (lab-proven)¶
FINALIZE/IMAGE_IDmust survive re-exec. Parent envFINALIZE=1and an explicitIMAGE_IDmust not be clobbered by script defaults (fixed inbuild-golden-image.sh). Otherwise “finalize” restarts install instead of capturing, or writes the wrong catalog ID.- One-minute
readyis not a golden. Real install + Sysprep is hours. A build that flips toreadyimmediately often captured a mid-Setup disk — VNC shows “Install Windows / restarted unexpectedly”, guest never answers ARP/RDP on10.0.2.2. Withguestkitinstalled on the build host, this is now caught automatically: the gate's EFI/BCD checks (BOOT-012/013/014) flagcertified=falseon exactly this class of disk — see Boot-readiness gate above. - Progress stuck at ~58% often means dockur is still installing (watch
:8066); do not finalize early. - Podman is supported for builds on hosts without Docker.
- CDI PVC size / volumeMode: virtual disks can be ~80 Gi — bootstrap with ≥96 Gi.
Match Filesystem vs Block between DataSource and clone targets or KubeVirt returns
IncompatibleVolumeModes. - Catalog aliases: do not point multiple image IDs at one qcow2 unless the content truly
matches (e.g.
windows-11-pro≠ Tiny11 Core).
1b. Build details (dockur VERSION codes)¶
Version → Kryton catalog ID¶
VERSION (dockur) |
Kryton image ID |
|---|---|
2025 |
windows-server-2025 |
2022 |
windows-server-2022 |
11e |
windows-11-enterprise |
11 |
windows-11-pro |
10 |
windows-10-pro |
2019 |
windows-server-2019 |
Override with KRYTON_IMAGE_ID=my-custom-image.
2. Bootstrap into Kubernetes (CDI)¶
API (from a ready golden build)¶
On a kubevirt krytond with kubectl/virtctl and kubeconfig:
curl -X POST http://127.0.0.1:9088/api/v1/golden/<build-id>/bootstrap
The Images page Publish to KubeVirt button runs the same path: upload the captured qcow2 and create DataSource <imageNamespace>/<imageId>.
Upload from local file¶
KRYTON_WINDOWS_IMAGE=./out/windows-2025-golden.qcow2 \
KRYTON_IMAGE_ID=windows-server-2025 \
./scripts/bootstrap-kubevirt-images.sh
Or via Make:
make bootstrap-kubevirt IMAGE=./out/windows-2025-golden.qcow2 ID=windows-server-2025
HTTP import (WinForge-style publish)¶
Upload the qcow2 to HTTPS-accessible storage, then:
KRYTON_IMAGE_URL=https://artifacts.example.com/windows-server-2025-golden.qcow2 \
KRYTON_IMAGE_ID=windows-server-2025 \
./scripts/bootstrap-kubevirt-images.sh --http
See deploy/kubevirt/datasource-http.yaml.example for a hand-crafted manifest.
3. Full automated setup¶
Chain build → bootstrap → Kryton API → smoke-test VM:
# If you already have a golden qcow2:
make setup-kubevirt IMAGE=./out/windows-11-enterprise-golden.qcow2
# Build first, then setup (interactive — waits for Sysprep):
make build-golden VERSION=11e
# ... complete Sysprep in guest ...
make build-golden VERSION=11e FINALIZE=1
make setup-kubevirt IMAGE=./out/windows-11e-golden.qcow2
setup-kubevirt.sh also accepts --http --url https://... when the image is already published.
Verify¶
kubectl -n kryton-images get datasource,pvc
krytonctl doctor # should show all catalog images as ready
Then create a VM from the UI or API using the matching image ID.
WinForge relationship¶
| WinForge | Kryton |
|---|---|
scripts/build-golden.sh |
scripts/build-golden-image.sh |
deploy/k8s/windows-image-datasource.yaml |
deploy/kubevirt/datasource-http.yaml.example |
winforged API |
Not integrated — use Kryton kubevirt provider |
windows-images namespace |
kryton-images (KRYTON_IMAGE_NAMESPACE) |
WinForge remains useful as a standalone image factory; Kryton consumes its output via CDI.