Deploy Netboot.xyz on Docker: Build a HomeLab PXE Boot Server for OS Installs and Rescue Tools

HomeLab tutorial - IT technology blog
HomeLab tutorial - IT technology blog

Toss the Drawer Full of USB Flash Drives

Most homelabs harbor a graveyard of USB thumb drives. You probably have one with MemTest86, another with Clonezilla, two worn-out 8GB drives with outdated Ubuntu ISOs, and a mystery drive from 2021. When a Proxmox host drops into an emergency shell or a mini PC corrupts its bootloader, fixing it becomes an ordeal.

First, you dig through drawers for functional media. Then you wait ten minutes for BalenaEtcher to write a 4GB image at sluggish USB 2.0 speeds. Finally, you crawl behind a rack to plug it in.

Cheap flash drives also fail without warning. They wear out after a few dozen writes and love to throw I/O errors midway through decompressing a Linux kernel. If you manage a cluster of three or four nodes, physical installation media simply wastes your time.

How PXE Boot Decouples Hardware from Local Media

Server motherboards and modern desktop NICs do not need attached flash storage to start up. Network booting has been part of Intel’s PXE specification since 1999 and is built straight into UEFI and legacy BIOS chips. Instead of reading blocks from a local flash controller, the network card requests an executable bootloader from your LAN before the primary operating system ever loads.

The 4-Step PXE Handshake

  1. DHCP Discovery: The client machine powers on, initialises its NIC, and broadcasts a DHCP request asking for an IP address alongside PXE configuration tags.
  2. Next-Server Resolution: Your DHCP server assigns an IP (e.g., 192.168.1.120) and delivers DHCP Option 66 (the TFTP server IP) and Option 67 (the bootloader filename).
  3. Bootstrap Download: The client reaches out to the TFTP service over UDP port 69, fetches a tiny bootstrap program like netboot.xyz.efi (around 1.5MB), and runs it in memory.
  4. HTTP Asset Handoff: The bootloader switches from slow TFTP to standard HTTP. It fetches Linux kernels, initramfs files, and live squashfs payloads across your 1Gbps or 2.5Gbps network at line speed.

Historically, setting this up meant managing disjointed instances of tftpd-hpa, Syslinux, ISC-DHCP, and Nginx. Netboot.xyz rolls all those dependencies into a single, community-maintained iPXE suite. Wrapping it in a Docker container makes deployment take less than five minutes.

Deploying Netboot.xyz via Docker

Run this container on any machine that stays online 24/7—such as an existing storage server, mini PC, or management VM running Ubuntu 24.04 or Debian 12. The container footprint is minimal, idling at under 150MB of RAM.

Step 1: Directory Setup and Compose File

Create dedicated directories on the host to hold boot configurations and locally cached ISO assets:

sudo mkdir -p /opt/netbootxyz/config /opt/netbootxyz/assets
sudo chown -R 1000:1000 /opt/netbootxyz
cd /opt/netbootxyz

You must use host networking here. PXE relies on TFTP (UDP 69) and DHCP broadcast relays that do not pass cleanly through standard Docker bridge networks with NAT port forwarding. Create your docker-compose.yml file:

cat <<'EOF' > docker-compose.yml
services:
  netbootxyz:
    image: ghcr.io/netbootxyz/netbootxyz:latest
    container_name: netbootxyz
    network_mode: host
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
      - PORT_RANGE=30000:30010
      - SUBFOLDER=/
    volumes:
      - /opt/netbootxyz/config:/config
      - /opt/netbootxyz/assets:/assets
    restart: unless-stopped
EOF

Launch the stack in detached mode:

docker compose up -d
docker compose logs -f netbootxyz

Confirm that all three internal services are listening on the host. You should see UDP 69 (TFTP), TCP 80 (HTTP asset server), and TCP 3000 (web configuration UI):

ss -tuln | grep -E ':(69|80|3000)'

Step 2: Pointing Your Router to Netboot.xyz

Your network clients cannot boot until your DHCP server advertises the PXE endpoints. Log in to your gateway—whether you run pfSense, OPNsense, OpenWrt, or a UniFi Dream Machine.

Assuming your Docker host uses the static IP 192.168.1.50, configure the following options in your DHCP server:

  • Next Server / Boot Server (Option 66): 192.168.1.50
  • Bootfile Name (Option 67): netboot.xyz.efi (for modern 64-bit UEFI machines) or netboot.xyz.kpxe (for legacy BIOS).

If your router runs Dnsmasq (common in OpenWrt, DD-WRT, and Pi-hole), you do not need to choose between UEFI and BIOS. You can match the architecture flag dynamically:

# Match client architecture (Option 93)
dhcp-match=set:bios,option:client-arch,0
dhcp-match=set:efi-x86_64,option:client-arch,7
dhcp-match=set:efi-x86_64,option:client-arch,9

# Direct clients to the appropriate binary
dhcp-boot=tag:bios,netboot.xyz.kpxe,,192.168.1.50
dhcp-boot=tag:efi-x86_64,netboot.xyz.efi,,192.168.1.50

Apply the settings and reload your DHCP service.

Step 3: Web UI and Custom Menus

Open http://192.168.1.50:3000 in your browser. The dashboard shows menu versions and lets you pull upstream asset updates directly from GitHub.

By default, Netboot.xyz fetches installer images from public internet mirrors. However, you can host local recovery images on your Docker server to save bandwidth and speed up boots. For example, a 1.2GB GParted live image loads in about 10 seconds over a Gigabit LAN link. Download the kernel and initrd into /opt/netbootxyz/assets/gparted/, then add a custom entry to /opt/netbootxyz/config/menus/custom.ipxe:

cat <<'EOF' > /opt/netbootxyz/config/menus/custom.ipxe
#!ipxe

:custom
clear custom_choice
menu Local HomeLab Rescue Utilities
item --gap -- Rescue Tools
item gparted GParted Live 1.6 (Local HTTP)
item shell Drop to iPXE Shell
item return Return to Main Menu
choose custom_choice || goto custom_exit
eval ${custom_choice}
goto custom

:gparted
echo Loading GParted Live over HTTP...
set base-url http://192.168.1.50/assets/gparted
kernel ${base-url}/vmlinuz boot=live config components union=overlay noswap net.ifnames=0 nosplash fetch=${base-url}/filesystem.squashfs
initrd ${base-url}/initrd.img
boot || goto custom

:shell
shell
goto custom

:return
chain --autofree boot.cfg ||
exit

:custom_exit
exit
EOF

Step 4: Booting Bare-Metal Nodes and VMs

Power on a target node—such as a Dell OptiPlex, Lenovo Tiny, or a fresh Proxmox VM. Hit F12 (or F11 depending on your motherboard) during POST to open the one-time boot menu. Select UEFI Network Boot.

Within three seconds, the client grabs an IP address, loads netboot.xyz.efi via TFTP, and renders the iPXE text menu. You can install Debian 12, provision a Proxmox VE node, wipe disks with DBAN, or clone partitions with Clonezilla without touching a single USB stick.

In a test lab with five physical hosts, this setup cuts machine reprovisioning from an hour-long chore down to a two-minute reboot. You avoid failed writes, outdated ISO files, and physical trips to your server rack.

Key Takeaways for Lab Administration

Network booting turns OS installation into an on-demand service. With Netboot.xyz in a Docker container and two DHCP tags configured on your router, any system connected to your switch can install an OS or boot a recovery environment immediately. Keep frequently used squashfs files in your local /assets volume to maximize throughput, verify that port 80 remains clear of host conflicts, and retire your flash drive drawer for good.

Share: