fix(proxmox): set guest DNS via cloud-init resolv_conf in vendor snippet - #342
Merged
Conversation
Statically-addressed VMs (ipconfig0, no DHCP) relied on each image's own network renderer to apply the DNS servers Proxmox provides. Minimal images silently dropped them: Alpine's busybox ifupdown writes dns-nameservers to the interfaces file, which only reaches /etc/resolv.conf when openresolv is installed (the stock Alpine cloud image lacks it), so guests booted with an empty /etc/resolv.conf and no working DNS. Extend the shared cloud-init vendor snippet to mirror the Proxmox host's /etc/resolv.conf into 'manage_resolv_conf: true' + 'resolv_conf.nameservers', forcing cloud-init to write /etc/resolv.conf directly regardless of the guest network renderer. This matches Proxmox's own host-resolv.conf fallback and fixes Alpine, NixOS and any other minimal image. Falls back to the previous snippet (no DNS block) when the host has no resolvers. Adds parse_resolv_conf_nameservers + build_vendor_snippet helpers with tests.
Instead of reading the Proxmox host's /etc/resolv.conf over SSH, force a fixed set of public resolvers into the cloud-init vendor snippet: Cloudflare, Google and Quad9, including their IPv6 variants. Drops the parse_resolv_conf_nameservers helper and the extra SSH round-trip.
…ruct Replace hand-concatenated YAML with CloudInitVendorData / CloudInitResolvConf structs serialised via serde_json. JSON is a strict subset of YAML, so the output (prefixed with the #cloud-config header) is valid cloud-config while eliminating fragile manual indentation/escaping. No new dependency.
…rde_yml Swap serde_json for serde_yml so the on-disk lnvps-vendor.yaml is proper cloud-config YAML instead of JSON-in-a-.yaml-file. Adds serde_yml to the workspace deps. Test now round-trips the snippet through serde_yml to assert structure rather than matching an exact string.
v0l
force-pushed
the
fix/cloudinit-guest-dns
branch
from
July 30, 2026 16:36
d56218f to
e9fc557
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Customers on the Alpine image (and other minimal images) booted with an empty
/etc/resolv.confand no working DNS.Root cause
LNVPS assigns IPs statically via Proxmox cloud-init (
ipconfig0, no DHCP). When no explicitnameserveris set, Proxmox falls back to the host's/etc/resolv.confand hands those servers to the guest's cloud-init network config — for every image. The failure is entirely guest-side rendering:resolvconf, so/etc/resolv.confgets populated (Ubuntu also has systemd-resolved fallback DNS masking the issue).ifupdownwith cloud-init's ENI renderer. It writesdns-nameserversinto/etc/network/interfaces.d/50-cloud-init, which only reaches/etc/resolv.confifopenresolv/resolvconfis installed — the stock Alpine cloud image doesn't ship it. So the DNS it was handed goes nowhere.Just setting
--nameserverexplicitly would not fix this, since the gap is in how Alpine applies whatever DNS it receives.Fix
Extend the shared cloud-init vendor snippet (
ensure_vendor_snippet) to force a fixed set of public resolvers into every guest:cloud-init's
resolv_confmodule writes/etc/resolv.confdirectly, bypassing the guest network renderer. Fixes Alpine, NixOS, and any minimal image in one uniform change. The resolver list (Cloudflare, Google, Quad9 — IPv4 + IPv6) is a fixed constant; no host SSH read is performed.Adds pure helper
build_vendor_snippetwith unit tests.Caveats
resolv_confmodule runs per-instance (once), so this fixes newly provisioned VMs and reinstalls. Existing affected VMs need a reinstall (or manualcloud-init clean/ resolv.conf edit)./etc/resolv.confreplaced with a direct file — benign for a VPS.Tests
test_build_vendor_snippet— empty list, an IPv4+IPv6 pair, and the production constant (asserts both IPv4 and IPv6 entries)All
host::proxmoxtests pass.