This is my scratchpad for the ongoing devlog on provisioning a physical bare metal node using Bifrost and Ironic, targeting an old Lenovo laptop over a wired network. Now let's see what I have been up to so far in respect to it.
Following up from the virtual provisioning run I did with Bifrost, I wanted to go further and point the same stack at real physical hardware. I had an old Lenovo G50-70 sitting around doing nothing, so that became the target. What followed was a full morning to afternoon of network topology wrangling, BIOS configuration, PXE boot debugging, and one very stubborn Realtek NIC. I didn't land where I wanted, but at least I learned more about the actual mechanics of bare metal provisioning than the virtual run ever could have taught me.
Figuring out the network topology
The first question was how to get the Dell running Bifrost and the Lenovo on the same network. Bifrost needs Layer 2 connectivity to serve DHCP and PXE boot files to target machines, and my usual setup had the Dell on Starlink via wifi. The Lenovo needed to be on the same flat network.
I borrowed a friend's MTN 5G router, plugged the Lenovo into it via ethernet, and connected the Dell to its wifi.
Running ip addr show on both machines confirmed the Dell landed at 192.168.0.144 and the Lenovo at 192.168.0.143,
both on the MTN router's 192.168.0.x subnet. I ran a ping confirming both reachability.
The MTN router had its DHCP pool spanning 192.168.0.2 to 192.168.0.253, which would conflict with Bifrost's own DHCP server
if I left it that way. Two DHCP servers on the same network means the Lenovo could get an IP from the wrong one during PXE boot
and never receive PXE boot options. I logged into the router admin page at 192.168.0.1, temporarily disconnected the mobile data to make
the settings editable, and narrowed the pool to 192.168.0.2-192.168.0.150. That left 192.168.0.200-192.168.0.230 free for Bifrost to use exclusively.
What the Lenovo has and doesn't have
Before enrolling anything, I needed to understand the hardware. Running sudo dmidecode -t 38 on the Lenovo returned nothing meaningful,
which confirmed what I suspected, no IPMI or BMC controller. Maybe old consumer laptops don't have out-of-band management interfaces.
This ruled out the ipmi driver entirely and meant I'd be using manual-management, which is Bifrost's driver for hardware where you
can't control power remotely. Ironic handles network boot configuration and image deployment, but you physically press the power button yourself.
I also grabbed the ethernet MAC address and confirmed the boot mode:
[ -d /sys/firmware/efi ] && echo "UEFI" || echo "BIOS"
cat /sys/class/net/enp1s0/address
Boot mode came back as BIOS/Legacy, and the MAC for PXE booting is 28:d2:44:ea:26:ef.
Both of these mattered for the BIOS configuration and the inventory file.
Reinstalling Bifrost for the physical network
The existing Bifrost install from the virtual run was bound to virbr0 on 192.168.122.x, which was completely wrong for this usecase.
I reinstalled with the correct parameters pointing at the physical network:
sudo ./bifrost-cli install \
--network-interface wlp0s20f3 \
--dhcp-pool 192.168.0.200-192.168.0.230 \
--hardware-types ipmi,redfish,manual-management \
--legacy-boot
The --legacy-boot flag was necessary because the Lenovo runs BIOS mode, not UEFI. The --hardware-types flag explicitly included manual-management
since it's not in the default set. After the install completed with failed=0, I verified Ironic was up and all three drivers were active:
source /opt/stack/bifrost/bin/activate
export OS_CLOUD=bifrost
baremetal driver list
+-------------------+-------+
| name | hosts |
+-------------------+-------+
| ipmi | 0xull |
| manual-management | 0xull |
| redfish | 0xull |
+-------------------+-------+
BIOS configuration
Before enrolling the Lenovo, I needed to configure it to PXE boot first. I powered it off, powered it back on, and pressed F2 repeatedly to enter the InsydeH20 BIOS setup. The Information page confirmed this was a Lenovo G50-70 with an Intel i3-4030U and 4GB RAM.

Navigating to the Boot tab, I found the Legacy boot order had the SATA HDD first, followed by the DVD drive, and finally the network boot device labeled "Realtek PXE B01 D00". Crucially, PXE Boot to LAN was already enabled. I moved the network boot entry to the top using Fn+F6, saved with Fn+F10, and let the machine reboot.

Enrolling the Lenovo
With the BIOS configured, I created the node inventory file. The first enrollment attempt failed immediately with Expected UUID for uuid
because I hadn't included a UUID in the JSON. Fixed by generating one with uuidgen and adding it:
{
"lenovo-g50": {
"uuid": "de071ae3-3469-40ca-8fe8-0856f791153f",
"name": "lenovo-g50",
"driver": "manual-management",
"driver_info": {},
"nics": [{"mac": "28:d2:44:ea:26:ef"}],
"ipv4_address": "192.168.0.200",
"ipv4_subnet_mask": "255.255.255.0",
"ipv4_gateway": "192.168.0.1",
"ipv4_nameserver": "8.8.8.8",
"properties": {
"cpu_arch": "x86_64",
"ram": "4096",
"disk_size": "298",
"cpus": "4"
}
}
}
Running ./bifrost-cli enroll ~/bifrost/lenovo-inventory.json got further but timed out with the node in clean failed.
Automated cleaning had tried to run, Ironic had attempted to power on the node remotely to boot the IPA cleaning ramdisk,
which manual-management fundamentally can't do. The fix was straightforward:
sudo sed -i 's/automated_clean = true/automated_clean = false/' /etc/ironic/ironic.conf
sudo systemctl restart ironic
With cleaning disabled, I moved the node through the state machine manually. The node was stuck in clean failed and locked mid-transition,
so I had to update the state directly in the database to break it out:
sudo mysql -u ironic -p<password> ironic -e \
"UPDATE nodes SET provision_state='manageable', \
target_provision_state=NULL, reservation=NULL \
WHERE name='lenovo-g50';"
sudo systemctl restart ironic
baremetal node provide lenovo-g50
After that, the node reached available cleanly:
+--------------------------------------+------------+---------------+-------------+-----------------+-------------+
| uuid | name | instance_uuid | power_state | provision_state | maintenance |
+--------------------------------------+------------+---------------+-------------+-----------------+-------------+
| de071ae3-3469-40ca-8fe8-0856f791153f | lenovo-g50 | None | power off | available | False |
+--------------------------------------+------------+---------------+-------------+-----------------+-------------+
I wasn't sure but power off with manual-management is expected (right?). Ironic can't check power state without a BMC, so it just shows the last known state.
Preparing the image
I downloaded CentOS Stream 9 directly to Ironic's httpboot directory so the IPA ramdisk could fetch it locally rather than trying to resolve external DNS:
sudo wget https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-latest.x86_64.qcow2 \
-O /var/lib/ironic/httpboot/centos-stream9.qcow2
sudo md5sum /var/lib/ironic/httpboot/centos-stream9.qcow2
Checksum came back as 5d9f3077692f0c90844088a2f0e4f12d.
The iPXE problem
The first deploy attempts used Bifrost's default ipxe boot interface. The Lenovo was getting DHCP offers and accepting them,
then downloading undionly.kpxe via TFTP, but then aborting in a loop. I saw this through the dnsmasq logs:
DHCPACK(wlp0s20f3) 192.168.0.205 28:d2:44:ea:26:ef
error 0 TFTP Aborted received from 192.168.0.205
sent /var/lib/tftpboot/undionly.kpxe to 192.168.0.205
sent /var/lib/tftpboot/undionly.kpxe to 192.168.0.205
I believe the Lenovo's Realtek NIC has an old PXE ROM. undionly.kpxe loaded the iPXE binary using the NIC's native UNDI stack,
but then iPXE tried to make HTTP requests to fetch boot.ipxe from Bifrost's nginx server. The old Realtek UNDI implementation couldn't handle HTTP,
so it aborted and started the cycle again. I confirmed nginx was accessible from the Lenovo, so it wasn't a network reachability problem.
It was a firmware limitation I suspected.
The fix was switching the node's boot interface from ipxe to pxe. I also had to do this via the database because the node was mid-transition and
the API wouldn't accept the update:
sudo mysql -u ironic -p<password> ironic -e \
"UPDATE nodes SET provision_state='manageable', \
target_provision_state=NULL, reservation=NULL, \
boot_interface='pxe' WHERE name='lenovo-g50';"
sudo systemctl restart ironic
Breaking through with pxelinux
Switching to pxe boot interface changed everything. Instead of undionly.kpxe, Bifrost now served pxelinux.0 via TFTP,
which the Lenovo's old ROM could actually work with. I also needed to install the syslinux packages since they weren't on the system:
sudo apt-get install -y pxelinux syslinux-common
sudo cp /usr/lib/PXELINUX/pxelinux.0 /var/lib/tftpboot/
sudo cp /usr/lib/syslinux/modules/bios/ldlinux.c32 /var/lib/tftpboot/
The next deploy attempt produced this in the dnsmasq logs:
sent /var/lib/tftpboot/pxelinux.0 to 192.168.0.205
sent /var/lib/tftpboot/ldlinux.c32 to 192.168.0.205
file /var/lib/tftpboot/pxelinux.cfg/42bd640e-8635-e411-a818-28d244ea26ef not found
sent /var/lib/tftpboot/pxelinux.cfg/01-28-d2-44-ea-26-ef to 192.168.0.205
sent /var/lib/tftpboot/de071ae3-3469-40ca-8fe8-0856f791153f/deploy_kernel to 192.168.0.205
pxelinux.0 loaded, ldlinux.c32 loaded, the config file was found and served, and the 12MB deploy kernel transferred successfully.
This was a genuine milestone. Real physical hardware, PXE booting from a config generated by Ironic, downloading a kernel over TFTP from the Dell.
The entire provisioning pipeline was working up to that point.
Where it stalled
The 304MB IPA ramdisk was a different story. Every attempt to transfer it via TFTP failed after 20 to 27 minutes:
sent .../deploy_kernel to 192.168.0.205
failed sending .../deploy_ramdisk to 192.168.0.205
The Lenovo is connected via ethernet to the router, so signal strength isn't the issue. The problem is the Realtek PXE ROM's TFTP implementation has a timeout or buffer limitation that can't sustain a 304MB transfer. The 12MB kernel completes before the ROM gives up. The ramdisk doesn't.
So far, the network path is solid, the pipeline (I believe) is working, and the deploy kernel transfers cleanly. It's specifically the ROM firmware standing between where I got to and a completed provisioning.
What I actually learned
Despite not completing the deployment, this attempt actually taught me things the virtual run couldn't. Seeing the actual DHCP handshake between a physical NIC and Bifrost's dnsmasq, watching the PXE config file get served over TFTP, and having a kernel actually load from the Dell onto the Lenovo's CPU, made the conceptual chain from "API call" to "physical machine responding" concrete in a way that's hard to get from watching VM logs.
This's a good place to stop for now, and obviously not the end, because I would love to boot an OS through the network so badly. I have so many times boot OS for my friends and myself through a live USB stick but never through the network.
Links:
- Bifrost docs -- https://docs.openstack.org/bifrost/2026.1/
- manual-management in Bifrost -- https://opendev.org/openstack/bifrost/src/branch/master/playbooks/roles/bifrost-ironic-install/README.md
- CentOS Stream 9 cloud images -- https://cloud.centos.org/centos/9-stream/x86_64/images/
- iPXE -- https://ipxe.org/