<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Bootstrap on The Nested Lab</title>
    <link>https://thenestedlab.com/tags/bootstrap/</link>
    <description>Recent content in Bootstrap on The Nested Lab</description>
    <generator>Hugo</generator>
    <language>en-gb</language>
    <lastBuildDate>Wed, 16 Sep 2026 07:20:00 +0100</lastBuildDate>
    <atom:link href="https://thenestedlab.com/tags/bootstrap/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>VPC subnets have no DHCP — and that&#39;s fine</title>
      <link>https://thenestedlab.com/posts/vpc-subnets-have-no-dhcp/</link>
      <pubDate>Wed, 16 Sep 2026 07:20:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/vpc-subnets-have-no-dhcp/</guid>
      <description>The nested-ESXi appliance sat at &amp;lsquo;waiting for DHCP&amp;rsquo; forever. VPC subnets don&amp;rsquo;t hand out addresses — the VM Service does, through bootstrap providers. cloud-init for Linux, sysprep for Windows, OVF guestinfo for appliances, and the per-vmk gateway detail that makes the Host Client tell the truth.</description>
      <content:encoded><![CDATA[<p>The second trap from <a href="/posts/nested-esxi-nsx-vpc/">part 1</a> deserves its
own short post, because it catches everything, not just ESXi: <strong>a VPC
subnet has DHCP deactivated.</strong> Drop a stock appliance onto one and it will
boot, sit at &ldquo;waiting for DHCP&rdquo;, and wait politely until the heat death of
the universe.</p>
<p>This isn&rsquo;t a gap. It&rsquo;s the model: NSX allocates the address at the <em>port</em>
and pins it there with address bindings; the <em>guest</em> has to be told what
it was given. The VM Service does that telling through <strong>bootstrap
providers</strong>, and once you know the three of them, static addressing stops
being a chore and starts being a feature.</p>
<h2 id="three-providers-three-guest-types">Three providers, three guest types</h2>
<table>
	<thead>
			<tr>
					<th>Guest</th>
					<th>Provider</th>
					<th>Carries</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Linux</td>
					<td><code>cloudInit</code></td>
					<td>user-data (users, <code>write_files</code>, <code>runcmd</code>) + network config</td>
			</tr>
			<tr>
					<td>Windows</td>
					<td><code>sysprep</code></td>
					<td>unattend XML / sysprep spec, identity, network</td>
			</tr>
			<tr>
					<td>Appliances (OVF)</td>
					<td><code>vAppConfig</code></td>
					<td>OVF properties (<code>guestinfo.*</code>) the appliance reads on boot</td>
			</tr>
	</tbody>
</table>
<p>All three are <strong>typed fields on the <code>VirtualMachine</code> object</strong>, not bolt-on
customisation specs. The network side is already known to the platform —
the VM Service knows which subnet each interface landed on and what NSX
allocated — so for cloud-init and sysprep the addressing is injected for
you. Appliances are the exception, because each one has its own idea of
which properties it wants.</p>
<h2 id="appliances-vappconfig">Appliances: vAppConfig</h2>
<p>The nested-ESXi appliance reads <code>guestinfo.*</code> OVF properties. In the VM
Service spec that&rsquo;s:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">bootstrap</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">vAppConfig</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="nt">guestinfo.hostname,  value</span><span class="p">:</span><span class="w"> </span>{<span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;esx01.pod-a.res.lab&#34;</span>}}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="nt">guestinfo.ipaddress, value</span><span class="p">:</span><span class="w"> </span>{<span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;172.30.0.40&#34;</span>}}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="nt">guestinfo.netmask,   value</span><span class="p">:</span><span class="w"> </span>{<span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;255.255.255.224&#34;</span>}}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="nt">guestinfo.gateway,   value</span><span class="p">:</span><span class="w"> </span>{<span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;172.30.0.33&#34;</span>}}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="nt">guestinfo.vlan,      value</span><span class="p">:</span><span class="w"> </span>{<span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1610&#34;</span>}}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="nt">guestinfo.dns,       value</span><span class="p">:</span><span class="w"> </span>{<span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;10.20.52.1&#34;</span>}}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="nt">guestinfo.ssh,       value</span><span class="p">:</span><span class="w"> </span>{<span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;True&#34;</span>}}<span class="w">
</span></span></span></code></pre></div><p>The one that trips people: <strong>the address you give must be the one NSX
allocated to the port.</strong> In a standard subnet that&rsquo;s enforced by
SpoofGuard; on a trunk subnet the VLAN subnets have their own allocations
and you&rsquo;re choosing addresses within them. Either way, pick from the
realized range — and remember <a href="/posts/nested-esxi-nsx-vpc/">recreating a VM reallocates its
addresses</a>, so the fixed <code>.40</code>/<code>.41</code> in a
<a href="/posts/three-datacenters-one-ip-plan/">deterministic pod</a> is a design
choice, not luck.</p>
<h2 id="linux-cloud-init">Linux: cloud-init</h2>
<p>A Secret holding user-data, referenced from the VM:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">bootstrap</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloudInit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cloudConfig</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">users</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="l">... a local user with a key ... ]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">write_files</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/var/www/html/index.html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">content</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;shared-svc repo01\n&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">runcmd</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="p">[</span><span class="l">systemctl, enable, --now, nginx]</span><span class="w">
</span></span></span></code></pre></div><p>Networking arrives via the platform&rsquo;s own network-config; you don&rsquo;t
write it. The <code>svc-repo01</code> VM from <a href="/posts/shared-services-for-isolated-tenants/">the shared-services
post</a> was exactly this —
<code>write_files</code> + <code>runcmd</code>, web page verified from three pods.</p>
<h2 id="windows-sysprep">Windows: sysprep</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">bootstrap</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sysprep</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">sysprep</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">guiUnattended</span><span class="p">:</span><span class="w"> </span>{<span class="nt">autoLogon</span><span class="p">:</span><span class="w"> </span><span class="nt">true, autoLogonCount</span><span class="p">:</span><span class="w"> </span><span class="nt">1, timeZone</span><span class="p">:</span><span class="w"> </span><span class="m">85</span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">identification</span><span class="p">:</span><span class="w"> </span>{<span class="nt">joinWorkgroup</span><span class="p">:</span><span class="w"> </span><span class="l">WORKGROUP}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">userData</span><span class="p">:</span><span class="w"> </span>{<span class="nt">fullName</span><span class="p">:</span><span class="w"> </span><span class="nt">Lab, orgName</span><span class="p">:</span><span class="w"> </span><span class="nt">Lab, computerName</span><span class="p">:</span><span class="w"> </span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">win01}}</span><span class="w">
</span></span></span></code></pre></div><p>Or <code>rawSysprep</code> with an unattend XML in a Secret if you already have one.
The ISO from <a href="/posts/nested-esxi-via-vcfa-all-apps/">the blueprint post</a>
can ride along as a declarative <code>hardware.cdrom</code> — handy for tools and
agents on first boot.</p>
<h2 id="the-esxi-footnote-one-gateway-many-vmks">The ESXi footnote: one gateway, many vmks</h2>
<p>Once the appliance is up and you add vMotion and vSAN vmks on their own
subnets, you hit a detail that makes the Host Client <em>look</em> wrong:</p>
<p><img alt="Host Client: vmk0/1/2, one service each" loading="lazy" src="/images/ui/u12-hostclient-vmk-adapters.jpg"></p>
<p>ESXi&rsquo;s default TCP/IP stack has <strong>one</strong> default gateway — vmk0&rsquo;s <code>.33</code> —
and the UI repeats it on every vmk row. Same-subnet vMotion never uses a
gateway so nothing breaks, but each NSX subnet <em>does</em> have its own
gateway, and cross-subnet traffic from vmk1/vmk2 would take the wrong exit.
Set per-vmk override gateways:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">esxcli network ip interface ipv4 set -i vmk1 -t static -I 172.30.0.70  -N 255.255.255.224 -g 172.30.0.65
</span></span><span class="line"><span class="cl">esxcli network ip interface ipv4 set -i vmk2 -t static -I 172.30.0.100 -N 255.255.255.224 -g 172.30.0.97
</span></span></code></pre></div><p>Now the display is truthful and the routing is correct. (The full-realism
alternative is a dedicated <code>vmotion</code> netstack for vmk1; I kept the default
stack so the service tags stay visible in the Host Client.)</p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>For the business, &ldquo;no DHCP&rdquo; translates into something security and
operations teams both want: <strong>predictable addressing</strong>. Every environment
has a known address plan, firewall rules can be written once, and nothing
turns up on the network with an address nobody expected. Bootstrap
providers deliver the second benefit — images stay generic and
configuration is injected at deploy time, so there are fewer golden images
to maintain and far less drift between environments.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li><strong>No DHCP in VPC subnets</strong> — by design. NSX allocates at the port; the
guest is told via a bootstrap provider.</li>
<li><code>cloudInit</code> (Linux), <code>sysprep</code> (Windows), <code>vAppConfig</code> (appliances) —
typed fields on the VM, not customisation specs.</li>
<li>Appliance addresses must match the <strong>realized</strong> subnet; fix the order
of subnet creation if you want fixed addresses across pods.</li>
<li>ESXi has <strong>one</strong> default gateway per stack. Set <code>-g</code> per vmk, or the
Host Client lies to you and cross-subnet traffic exits wrong.</li>
<li>A static IP plan is a feature in a lab: it&rsquo;s what makes screenshots,
runbooks and pods identical.</li>
</ul>
<p><em>Companion to <a href="/posts/nested-esxi-nsx-vpc/">nested ESXi inside an NSX VPC</a>.</em></p>
<hr>
<p><em>Lab environment; opinions my own.</em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
