These are steps that I used on an Ubuntu 20.04 LTS (Focal Fossa) 64-bit system to build an amd64 (64-bit) Debian 10 (Buster) live environment that can boot from CD or USB.

The live environment generated by this guide is bootable with legacy BIOS or modern EFI hardware.

See here for the previous version of the guide, which only used GRUB and was written for x86 (32-bit) Debian 9 using Ubuntu 18.

On the loss of 32-bit x86 support

Modern Linux operating systems like Ubuntu, Debian, and Arch do not support 32-bit architecture in the kernels on their default live CDs anymore. Neither does this guide. Welcome to the future! It is still entirely possible to adapt this guide for x86 (32-bit) support, but that is an exercise for the reader.

A screenshot from VirtualBox showing a 32-bit Ubuntu VM failing to boot the latest installer iso and displaying an error message
A screenshot from VirtualBox showing a 32-bit Ubuntu VM failing to boot the latest installer iso and displaying an error message

I wrote this guide more for personal educational purposes than anything. It is not necessarily the fastest guide or the best guide for your needs. There are many other apps, tutorials, walkthroughs, and methods that better accomplish what is in this guide.[1][2][3][4][5][6][7] I hope this guide is helpful all the same.

The Debian DebianCustomCD documentation is more informative than this article could ever be.

Warning: I have highlighted all the places you should be in the [chroot] environment. Be careful! Running some of these commands on your local environment instead of in the chroot can damage your system.

Prerequisites

Install applications we need to build the environment.

sudo apt install \
    debootstrap \
    squashfs-tools \
    xorriso \
    isolinux \
    syslinux-efi \
    grub-pc-bin \
    grub-efi-amd64-bin \
    mtools

Create a directory where we will store all of the files we create throughout this guide.

mkdir -p $HOME/LIVE_BOOT

Bootstrap and Configure Debian

Set up the base Debian environment. I am using buster for my distribution and amd64 for the architecture. Consult the list of debian mirrors.

Please change the URL in this command if there is a mirror that is closer to you.

sudo debootstrap \
    --arch=amd64 \
    --variant=minbase \
    buster \
    $HOME/LIVE_BOOT/chroot \
    http://ftp.us.debian.org/debian/

Chroot to the Debian environment we just bootstrapped.

sudo chroot $HOME/LIVE_BOOT/chroot

[chroot] Set a custom hostname for your Debian environment.

echo "debian-live" > /etc/hostname

[chroot] Figure out which Linux Kernel you want in the live environment.

apt-cache search linux-image

[chroot] I chose the image linux-image-amd64. I also believe live-boot is a necessity. systemd-sys (or an equivalent) is also necessary to provide init.

apt update && \
apt install --no-install-recommends \
    linux-image-amd64 \
    live-boot \
    systemd-sysv

[chroot] Install programs of your choosing, and then run apt clean to save some space. I use --no-install-recommends to avoid superfluous packages. You should decide what you need for your environment.

Read Debian’s ReduceDebian article for tips on reducing the size of your Debian environment if size is important and you want a minimal and compact installation. Please note that some live environments like Alpine Linux, Tiny Core Linux, and Puppy Linux are specifically optimized for a tiny footprint.

Although this article results in a relatively tiny live environment, generating an environment that is only a couple dozen MB large takes additional effort not covered in this article.

apt install --no-install-recommends \
    network-manager net-tools wireless-tools wpagui \
    curl openssh-client \
    blackbox xserver-xorg-core xserver-xorg xinit xterm \
    nano && \
apt clean

[chroot] Set the root password. root will be the only user in this live environment by default, but you may add additional users as needed.

passwd root

[chroot] Exit the chroot.

exit

Create directories that will contain files for our live environment files and scratch files.

mkdir -p $HOME/LIVE_BOOT/{staging/{EFI/boot,boot/grub/x86_64-efi,isolinux,live},tmp}

Compress the chroot environment into a Squash filesystem.

sudo mksquashfs \
    $HOME/LIVE_BOOT/chroot \
    $HOME/LIVE_BOOT/staging/live/filesystem.squashfs \
    -e boot

Copy the kernel and initramfs from inside the chroot to the live directory.

cp $HOME/LIVE_BOOT/chroot/boot/vmlinuz-* \
    $HOME/LIVE_BOOT/staging/live/vmlinuz && \
cp $HOME/LIVE_BOOT/chroot/boot/initrd.img-* \
    $HOME/LIVE_BOOT/staging/live/initrd

Prepare Boot Loader Menus

Create an ISOLINUX (Syslinux) boot menu. This boot menu is used when booting in BIOS/legacy mode.

cat <<'EOF' >$HOME/LIVE_BOOT/staging/isolinux/isolinux.cfg
UI vesamenu.c32

MENU TITLE Boot Menu
DEFAULT linux
TIMEOUT 600
MENU RESOLUTION 640 480
MENU COLOR border       30;44   #40ffffff #a0000000 std
MENU COLOR title        1;36;44 #9033ccff #a0000000 std
MENU COLOR sel          7;37;40 #e0ffffff #20ffffff all
MENU COLOR unsel        37;44   #50ffffff #a0000000 std
MENU COLOR help         37;40   #c0ffffff #a0000000 std
MENU COLOR timeout_msg  37;40   #80ffffff #00000000 std
MENU COLOR timeout      1;37;40 #c0ffffff #00000000 std
MENU COLOR msg07        37;40   #90ffffff #a0000000 std
MENU COLOR tabmsg       31;40   #30ffffff #00000000 std

LABEL linux
  MENU LABEL Debian Live [BIOS/ISOLINUX]
  MENU DEFAULT
  KERNEL /live/vmlinuz
  APPEND initrd=/live/initrd boot=live

LABEL linux
  MENU LABEL Debian Live [BIOS/ISOLINUX] (nomodeset)
  MENU DEFAULT
  KERNEL /live/vmlinuz
  APPEND initrd=/live/initrd boot=live nomodeset
EOF

Create a second, similar, boot menu for GRUB. This boot menu is used when booting in EFI/UEFI mode.

cat <<'EOF' >$HOME/LIVE_BOOT/staging/boot/grub/grub.cfg
search --set=root --file /DEBIAN_CUSTOM

set default="0"
set timeout=30

# If X has issues finding screens, experiment with/without nomodeset.

menuentry "Debian Live [EFI/GRUB]" {
    linux ($root)/live/vmlinuz boot=live
    initrd ($root)/live/initrd
}

menuentry "Debian Live [EFI/GRUB] (nomodeset)" {
    linux ($root)/live/vmlinuz boot=live nomodeset
    initrd ($root)/live/initrd
}
EOF

Create a third boot config. This config will be an early configuration file that is embedded inside GRUB in the EFI partition. This finds the root and loads the GRUB config from there.

cat <<'EOF' >$HOME/LIVE_BOOT/tmp/grub-standalone.cfg
search --set=root --file /DEBIAN_CUSTOM
set prefix=($root)/boot/grub/
configfile /boot/grub/grub.cfg
EOF
Why both GRUB and SYSLINUX?

Neither GRUB nor SYSLINUX are ideal on their own for both BIOS and EFI booting. Almost every major Linux distribution uses a combination of the two as they both excel in different areas.

That is why I have GRUB in addition to SYSLINUX in this guide.

Why grub.cfg and grub-standalone.cfg?

There are a few reasons we have grub-standalone.cfg in addition to grub.cfg.

We could embed all modules and information in the EFI partition, but I think it is more readable and maintainable to separate out the bare-minimum GRUB boot loader in EFI from the human-friendly boot configuration in the root of the device.

Also, I believe that some third-party tools (UNetbootin, YUMI, etc) expect/need to find a sane /boot/grub sort of path on the root device. This guide is not optimized for that use-case, but I want to follow (more or less) best/common practices.

Create a special file in staging named DEBIAN_CUSTOM. This file will be used to help GRUB figure out which device contains our live filesystem. This file name must be unique and must match the file name in our grub.cfg config.

touch $HOME/LIVE_BOOT/staging/DEBIAN_CUSTOM

Your LIVE_BOOT directory should now roughly look like this.

LIVE_BOOT/chroot/*tons of chroot files*
LIVE_BOOT/staging/live/initrd
LIVE_BOOT/staging/live/vmlinuz
LIVE_BOOT/staging/live/filesystem.squashfs
LIVE_BOOT/staging/isolinux/isolinux.cfg
LIVE_BOOT/staging/EFI/boot
LIVE_BOOT/staging/boot/grub/grub.cfg
LIVE_BOOT/staging/boot/grub/x86_64-efi
LIVE_BOOT/staging/DEBIAN_CUSTOM

Prepare Boot Loader Files

Copy BIOS/legacy boot required files into our workspace.

cp /usr/lib/ISOLINUX/isolinux.bin "${HOME}/LIVE_BOOT/staging/isolinux/" && \
cp /usr/lib/syslinux/modules/bios/* "${HOME}/LIVE_BOOT/staging/isolinux/"

Copy EFI/modern boot required files into our workspace.

cp -r /usr/lib/grub/x86_64-efi/* "${HOME}/LIVE_BOOT/staging/boot/grub/x86_64-efi/"

Generate an EFI bootable GRUB image.

grub-mkstandalone \
    --format=x86_64-efi \
    --output=$HOME/LIVE_BOOT/tmp/bootx64.efi \
    --locales="" \
    --fonts="" \
    "boot/grub/grub.cfg=$HOME/LIVE_BOOT/tmp/grub-standalone.cfg"

Create a FAT16 UEFI boot disk image containing the EFI bootloader. Note the use of the mmd and mcopy commands to copy our UEFI boot loader named bootx64.efi.

(cd $HOME/LIVE_BOOT/staging/EFI/boot && \
    dd if=/dev/zero of=efiboot.img bs=1M count=20 && \
    mkfs.vfat efiboot.img && \
    mmd -i efiboot.img efi efi/boot && \
    mcopy -vi efiboot.img $HOME/LIVE_BOOT/tmp/bootx64.efi ::efi/boot/
)

Create Bootable ISO/CD

The .iso file we create can be burned to a CD-ROM (optical media) and can also be written to a USB device with dd. The process here a bit complex, but that resultant behavior is common in many modern live environments such as the Ubuntu installation .iso file.

Please note that writing an .iso file to a USB device is not the same as installing the live environment directly to a USB device.

Generate the .iso disc image file.

xorriso \
    -as mkisofs \
    -iso-level 3 \
    -o "${HOME}/LIVE_BOOT/debian-custom.iso" \
    -full-iso9660-filenames \
    -volid "DEBIAN_CUSTOM" \
    -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \
    -eltorito-boot \
        isolinux/isolinux.bin \
        -no-emul-boot \
        -boot-load-size 4 \
        -boot-info-table \
        --eltorito-catalog isolinux/isolinux.cat \
    -eltorito-alt-boot \
        -e /EFI/boot/efiboot.img \
        -no-emul-boot \
        -isohybrid-gpt-basdat \
    -append_partition 2 0xef ${HOME}/LIVE_BOOT/staging/EFI/boot/efiboot.img \
    "${HOME}/LIVE_BOOT/staging"
Using UNetbootin, YUMI, or similar tools to install the ISO to USB

If you intend to use one of those tools or something similar please make sure the USB device is FAT32 formatted. I have done very basic FAT32 testing with YUMI and found that the .iso file generated by this guide does work.

If you want EFI support for your YUMI (or whatever) USB device you should verify that the tool you are using supports EFI at all. For example, YUMI has a specific version just for EFI support. Also, please try testing the tool you are using with a mainline Linux OS .iso file like Ubuntu or Debian’s and verify it works as you expect before trying to adapt the instructions in this guide.

I have not done extensive testing with these sorts of tools. I have only tested by using dd to directly transfer the .iso file to USB.

Citations