The Windows template factory
I use Jenkins, Kubernetes, and Packer to rebuild Windows Server templates in Proxmox every week. Each replacement is installed, updated, generalized, and verified before provisioning starts using it.
The current template stays available throughout the build. Provisioning resolves a stable OS tag, so I can replace the image underneath it without changing the provisioning pipeline.
Jenkins runs the factory. Packer builds the image.
A weekly Jenkins pipeline checks out the Packer definitions and build scripts from Git, then starts disposable build agents in Kubernetes. Each Windows release has its own agent and a pair of template slots in Proxmox.
Packer installs Windows into the inactive slot, applies Windows Updates, adds runtime initialization tooling, and runs Sysprep to generalize the guest. Vault supplies the build credentials. A Python lifecycle script checks the resulting template against the Proxmox API before it can be promoted.
A slow build does not hold up provisioning
Last successful build: September 15, 2026 · about 77 minutes. That is time spent preparing the next image. A request for a new server can still clone the current verified template while that work runs.
The separation comes from template tags. Provisioning asks for a role such as os-2025 instead of a fixed VM ID. That tag stays on the current template while its replacement is built in the inactive slot.
I still care about build time for capacity and backup scheduling. It just does not put image maintenance in the path of every new server request.
The templates are updated, not left to age
Weekly rebuilds create a fresh Windows image and apply updates before promotion. New servers inherit the updated baseline as soon as their provisioning run resolves the new live tag.
For an on-demand patch, a separate Jenkins job clones the current template into a working VM. Ansible applies Windows Updates over WinRM, runs Sysprep, and shuts the guest down before it is converted back into a template and promoted.
Both paths prepare the replacement separately from the image serving provisioning. Updating a template changes the baseline for future clones; existing servers still need their own patching.
- Jenkins + Kubernetes
- Schedule the rebuild and provide disposable, isolated Packer agents.
- Packer + Sysprep
- Build a clean, updated Windows image that is ready to clone.
- Ansible + WinRM
- Apply updates to a working copy when I need an on-demand template patch.
- Proxmox + tags
- Keep the current image available and let provisioning follow the verified replacement.
Why I stopped patching the same image in place
The first version repeatedly booted, patched, generalized, and shut down the same template. Pending Windows work and Sysprep failures accumulated until image maintenance could block provisioning.
I changed the design so that an image is a replaceable release artifact. The build can fail without taking the current template with it. The stable interface is the OS tag, backed by a verified image.
Build safeguards and recovery
- Build definitions. The repository contains 3 Windows variant definitions; the Jenkins matrix selects the weekly releases.
- Check the backup window. The factory reads active and scheduled Proxmox backups before starting a long build.
- Keep releases isolated. Each OS uses a separate template pair and Kubernetes agent. Matrix builds can run alongside one another; overlapping pipeline runs are disabled.
- Verify authoritative state. The lifecycle script reads the node configuration, because the cluster cache can briefly report a converted template as a VM.
- Bound failed work. Per-image timeouts stop a stalled build. Failure cleanup targets staging and leaves the current live template in place.
- Recover a promoted weekly image. Move the live role tag back to the retained previous template so subsequent provisioning uses it.