Provisioning a bare Linux VM — a dozen small lessons in engineering rigor
Context
The self-service catalog already offered two ready-made services (a web server, a database). This item is different in spirit: a bare Linux virtual machine, no pre-installed service, no hardening — just a machine for someone to install something on, break, and learn from.
Why it's harder than it sounds
Reusing an existing pipeline for a new kind of thing surfaces every assumption that pipeline was quietly making. A handful of examples that only showed up in practice, not on paper:
- A console that had been silently broken all along. Long before this item existed, opening a graphical console on any catalog VM produced a black screen with no error — caused by a display setting incompatible with the remote-console mechanism. Fixed across every template in the catalog, not just the new one, plus a second, independent text-console path as a fallback.
- No password, by design — and a real problem the moment someone loses their key. A bare VM only accepts SSH key-based logins, which is the right default — but it also means a user who fumbles their key or a network setting has no way back in. A separate, console-only recovery password was added: usable only from the graphical/text console, never valid over SSH, generated once, shown exactly once, and never stored anywhere afterward.
- A wrong assumption about which template everything else was built on. The two existing catalog items were assumed to share a base template with the new one — checked directly against the actual configuration rather than trusted, and turned out to be wrong. Worth stating as a general habit: verify a shared dependency before building on top of it, especially when the assumption is convenient.
- A permission that only reveals itself on first real use. A guest-agent permission needed to report a new VM's real address back to the user wasn't needed by the two earlier catalog items (they're reached by name through a proxy, never by raw address) — so its absence was invisible until this new item hit it directly.
- A keyboard layout bug in two unrelated layers at once. Getting a non-US keyboard layout right inside a remote console touched both the guest operating system's own configuration and a leftover setting on the virtualization layer that silently overrode it — fixed properly only once both layers were checked independently, rather than assuming a fix to one layer was the whole story.
- A tag with a forbidden character orphaned a virtual machine. A metadata tag format that looked fine on paper was silently rejected by the underlying platform's validation rules, leaving a VM invisible to its own cleanup process. Format fixed, orphan cleaned up by hand once found.
What I'd generalize from this
None of these individually are dramatic. What's worth remembering is the pattern: a pipeline that works well for two similar services will still surface new, unexpected requirements the moment you point it at something different in kind — and the only reliable way to find those requirements is a real end-to-end test, not a read-through of the code.
Alternatives considered
- Shipping this item pre-hardened (baseline security lockdown baked in) — deliberately rejected. The whole point of a bare VM is that it's bare; hardening exercises live elsewhere on this homelab, applied to machines built specifically for that purpose.