This guide covers deploying the PXE boot server using Docker containers.
The deployment consists of two containers:
- TFTP Server: Serves iPXE bootloaders (undionly.kpxe, ipxe.efi, snponly.efi)
- HTTP Server: Serves iPXE scripts, boot images, installation scripts, and cloud-init configurations
/srv/
├── images/
│ └── debian/ # Debian cloud images
│ ├── debian-13-generic-amd64.qcow2
│ └── debian-12-generic-amd64.qcow2
│
├── boot/ # HTTP-served content
│ ├── ipxe/ # iPXE menu scripts
│ ├── alpine/amd64/ # Alpine netboot files
│ ├── debian/ # Extracted boot files
│ │ ├── 13/amd64/
│ │ └── 12/amd64/
│ ├── images/debian/ # Symlinked cloud images
│ ├── scripts/ # Installation scripts
│ └── cloud-init/debian/ # Cloud-init configs
│ ├── 13/
│ └── 12/
│
└── tftp/ # TFTP bootloaders
├── undionly.kpxe
├── ipxe.efi
└── snponly.efi
- Docker and Docker Compose installed
- DHCP server that can be configured for PXE boot
- Server accessible on network (UDP/69 for TFTP, TCP/80 for HTTP)
All scripts and configuration files are mounted as volumes from your local repository. This allows you to edit files locally and simply restart containers without rebuilding.
Edit configuration files:
# Add SSH keys for Alpine rescue mode
vim config/alpine-rescue/authorized_keys
# Set root password (optional)
echo "yourpassword" > config/alpine-rescue/root_password
# Customize Debian cloud-init
vim cloud-init/debian/13/user-data
# Customize iPXE menu or install scripts
vim ipxe/menu.ipxe
vim scripts/install-debian-cloud-to-disk.shApply changes:
# Just restart the affected container (no rebuild needed!)
docker compose restart http
# Or restart all containers
docker compose restartdocker compose up -d
docker compose logs -f# Check HTTP server
curl http://localhost/health
curl http://localhost/boot/ipxe/menu.ipxe
# Check TFTP container
docker compose ps tftpISC DHCP:
next-server YOUR_SERVER_IP;
if option architecture-type = 00:00 {
filename "undionly.kpxe";
} elsif option architecture-type = 00:07 {
filename "ipxe.efi";
}
dnsmasq:
dhcp-boot=tag:BIOS,undionly.kpxe
dhcp-boot=tag:UEFI,ipxe.efi
tftp-server-name=YOUR_SERVER_IP
Boot a machine via network boot and verify the installation completes successfully.
docker compose logs -f http
docker compose logs -f tftpdocker compose restart http
docker compose restart tftpIf using volume mounts for configuration:
# Edit configuration files
vim config/alpine-rescue/authorized_keys
# Restart to regenerate overlay
docker compose restart httpForce re-download of boot images:
docker volume rm homelab-boot-server_images-data
docker volume rm homelab-boot-server_boot-data
docker compose restart http# View cached images
docker compose exec http ls -lh /srv/images/debian/
# View boot files
docker compose exec http ls -lh /srv/boot/debian/13/amd64/The structure supports multiple versions. To enable Debian 12:
-
Customize cloud-init:
vim cloud-init/debian/12/user-data
-
Update
scripts/download-boot-images.shto download Debian 12 image -
Rebuild:
docker compose up -d --build
The HTTP container generates configuration at startup:
- Alpine overlay (
/srv/boot/alpine/alpine.apkovl.tar.gz) built from:/opt/apkovl/(source files)/opt/config/alpine-rescue/(customizations)
To customize:
- Edit local files
- Ensure volume mount is configured in
docker-compose.yml - Restart container:
docker compose restart http - Overlay regenerates automatically
No rebuild required.
docker compose logs http
docker compose exec http nginx -tdocker compose logs tftp
docker compose exec tftp ls -lh /srv/tftp/docker compose exec http tail -f /var/log/download-boot-images.log- Verify DHCP points to correct server IP
- Check firewall allows UDP/69 and TCP/80
- Test TFTP manually:
tftp YOUR_SERVER_IP get undionly.kpxe
- Persistent Volumes: Images cached in Docker volumes for performance
- Network Mode: TFTP uses
hostnetwork mode for UDP compatibility - Resource Requirements: HTTP container needs ~10GB for image cache
- Security: Consider HTTPS, authentication, and network isolation
- Secrets: Use volume mounts to keep credentials out of images
- Base: debian:13.2-slim
- Size: ~100MB
- Purpose: Serves iPXE bootloaders only
- Network: Host mode (required for TFTP)
- Volumes:
tftp-data:/srv/tftp
- Base: nginx:1.29.4-trixie
- Size: ~500MB + images
- Purpose: Serves all boot files and installation scripts
- Network: Bridge mode
- Ports: 80/TCP
- Volumes:
images-data:/srv/images(persistent)boot-data:/srv/boot(can be regenerated)
- Health Check:
/healthendpoint every 30s
- INSTALL.md - Bare metal installation
- AUTOMATED-INSTALL.md - How automation works
- RESCUE-MODE.md - Alpine rescue mode