Netbooting a fleet of machines means putting GRUB's boot images where TFTP can serve them, and grub-mknetdir builds that tree in one pass. This guide builds a staging copy first, checks it, then leaves publishing to you as a separate, deliberate step. The examples use the installed grub-mknetdir from GRUB 2.12-1ubuntu7.3, supplied by the grub-common package on this machine.
grub-common package, enough disk space for the generated modules and font, and a TFTP root you can write to.Warning: a repeated run can overwrite files below the destination you give it. Build into a new staging directory first, check the result, then copy or publish the tree during a planned change window rather than pointing the command straight at a live TFTP root.
Three read-only checks, none of which need elevated privileges:
$ command -v grub-mknetdir
/usr/bin/grub-mknetdir
$ grub-mknetdir --version
grub-mknetdir (GRUB) 2.12-1ubuntu7.3
$ dpkg-query -W -f='${Package} ${Version}\n' grub-common
grub-common 2.12-1ubuntu7.3
The manpage describes the result as images under net_directory/subdir, with net_directory treated as the TFTP root. The command has no way to see your server's real TFTP directory, so you always pass it explicitly with --net-directory.
Set two shell variables, swapping in a real staging path of your own:
$ NETROOT=/srv/tftp-staging
$ SUBDIR=grub-test
NETROOT is the TFTP root.SUBDIR is a relative path below it, so files land under /srv/tftp-staging/grub-test. A named subdirectory keeps multiple boot trees apart and out of unrelated TFTP content.Create the staging root if it does not exist yet, then confirm you can actually write to it:
$ mkdir -p "$NETROOT"
$ test -w "$NETROOT" && echo "staging root is writable"
staging root is writable
Creating that staging root is the first command in this guide that changes filesystem state. Use sudo only if your chosen path is not already writable by your account.
Checkpoint: if that test fails, stop and fix ownership or pick a path your account owns. Do not work around a permission problem by writing to the production TFTP root as root instead.
Run the build with explicit source and destination:
$ grub-mknetdir --net-directory "$NETROOT" --subdir "$SUBDIR"
Netboot directory for i386-pc created. Configure your DHCP server to point to /srv/tftp-staging/grub-test/i386-pc/core.0
Netboot directory for x86_64-efi created. Configure your DHCP server to point to /srv/tftp-staging/grub-test/x86_64-efi/core.efi
Those platform lines are your completion signal, but treat the exact paths as an example: the platform list depends on which GRUB modules are installed on this host. A non-zero exit status means failure, even if some files were written.
Checkpoint: inspect the new tree before it goes anywhere near a client:
$ find "$NETROOT/$SUBDIR" -maxdepth 2 -type f -printf '%P %s bytes\n' | sort | head -20
$ test -s "$NETROOT/$SUBDIR/i386-pc/core.0" && echo "BIOS image exists"
$ test -s "$NETROOT/$SUBDIR/x86_64-efi/core.efi" && echo "EFI image exists"
BIOS image exists
EFI image exists
The BIOS image is core.0, the EFI one is core.efi. Do not rename either just to match a DHCP setting: configure the boot filename to match the path and firmware class your boot service actually expects.
Left alone, the command uses the GRUB platform data under /usr/lib/grub/<platform>, installs every module, every locale, the unicode font and the starfield theme, with compression left at auto for the core image. That is why a first tree is often much bigger than the two boot images alone.
Run with verbose output when you want an audit trail of exactly what got selected:
$ grub-mknetdir --net-directory "$NETROOT" --subdir "$SUBDIR" --verbose 2> grub-mknetdir.log
$ tail -n 5 grub-mknetdir.log
Netboot directory for i386-pc created. Configure your DHCP server to point to ...
Netboot directory for x86_64-efi created. Configure your DHCP server to point to ...
Keep that log in a file rather than scrolling past it. A line complaining that a locale file could not be opened is not proof the whole tree is broken; check the exit status and the required boot files instead, and investigate before publishing if the exit status is non-zero.
--install-modules=MODULES installs only the named modules and their dependencies.--locales=LOCALES limits which translations are included.--fonts=FONTS picks specific fonts instead of the default.The right module set is a property of your GRUB configuration, not something this command can guess for you. This example keeps only English and drops the theme:
$ grub-mknetdir \
--net-directory "$NETROOT" \
--subdir "$SUBDIR-minimal" \
--locales=en \
--fonts= \
--themes=
Empty values are valid syntax, but a minimal tree may be missing files your menu or boot flow needs. Treat this as an experiment: compare it against the default build and test both BIOS and EFI clients before replacing the larger tree.
--modules=MODULES is a different knob again: it pre-loads modules rather than controlling what gets installed. Add one only when your boot design actually calls for it, and verify the result.
The command can embed a DTB with --dtb=FILE, a public key with --pubkey=FILE, and SBAT metadata with --sbat=FILE. Each of these affects boot trust decisions, so check the ownership, permissions and provenance of the file before you pass it in, and record exactly which file you used.
Security warning: --disable-shim-lock turns off the shim lock verifier. Never reach for it to silence a signature or Secure Boot error; fix the trust chain with whoever owns the platform instead, and test any deliberate change on an isolated client first. A netboot directory is executable boot material, so treat changes to it like any other security-sensitive deployment change.
Only use --directory=DIR when you deliberately want a different GRUB module directory than the installed default. Pointing it somewhere arbitrary can produce a tree that looks complete but is missing the modules your configuration actually needs.
grub-mknetdir itself.When replacing an existing tree, keep a rollback copy and publish the new one under a separate subdirectory first. Switch the DHCP or boot-service setting only after clients have tested successfully, and keep the old subdirectory around until everyone agrees rollback is no longer needed.
To abandon a staging build you no longer want, check the path, then remove exactly that directory:
$ printf 'review before deletion: %s\n' "$NETROOT/$SUBDIR"
review before deletion: /srv/tftp-staging/grub-test
$ rm -r -- "$NETROOT/$SUBDIR"
Destructive action: that deletion is irreversible for the staging tree. Never point it at a production TFTP root, and never leave the variable unquoted.
grub-mknetdir --version matches the GRUB build you intended to test.core.0 and/or core.efi file exists and is non-empty for each client class.