Warning: I have stopped updating this article in favor of a similar, but updated, guide.

These are steps that I used on an Ubuntu 18.04 LTS (Bionic Beaver) 64-bit system to build an x86 Debian 9 (Stretch) live environment that I can boot from CD or USB.

These steps can be used to create a live environment that is BIOS bootable (MBR), UEFI bootable (GPT), or combination of both UEFI and BIOS bootable. Something unique about this guide is that Syslinux/Isolinux are not used. Only Grub boot equipment. This is done for consistency and to avoid mixing the two (Syslinux/Isolinux alone cannot accomplish everything covered in this article, but Grub can).

Here are some alternatives to my guide that may be better solutions for anyone reading this article: live-build, mkusb, UNetbootin, xixer, rufus, YUMI, Simple-cdd. You should also look at the Debian DebianCustomCD documentation as it will prove more informative than this article could ever be.

I wrote this guide more for educational purposes than anything. It is not necessarily the fastest guide or the best guide for your needs. I hope it is helpful all the same.

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-get install \
    debootstrap \
    squashfs-tools \
    xorriso \
    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 stretch for my distribution and i386 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=i386 \
    --variant=minbase \
    stretch \
    $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-686. I also believe live-boot is a necessity. systemd-sys (or an equivalent) is also necessary to provide init.

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

[chroot] Install programs of your choosing, and then run apt-get 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 Tiny Core Linux or 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-get install --no-install-recommends \
    network-manager net-tools wireless-tools wpagui \
    curl openssh-client \
    blackbox xserver-xorg-core xserver-xorg xinit xterm \
    nano && \
apt-get 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/{scratch,image/live}

Compress the chroot environment into a Squash filesystem.

sudo mksquashfs \
    $HOME/LIVE_BOOT/chroot \
    $HOME/LIVE_BOOT/image/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/image/vmlinuz && \
cp $HOME/LIVE_BOOT/chroot/boot/initrd.img-* \
    $HOME/LIVE_BOOT/image/initrd

Create a menu configuration file for grub. Please note that the insmod all_video line was needed in my testing to deal with a bug in UEFI booting for one of my machines. Perhaps not everyone will need that line, but I did.

This config instructs Grub to use the search command to infer which device contains our live environment. This seems like the most portable solution considering the various ways we may write our live environment to bootable media.

cat <<'EOF' >$HOME/LIVE_BOOT/scratch/grub.cfg

insmod all_video

search --set=root --file /DEBIAN_CUSTOM

set default="0"
set timeout=30

menuentry "Debian Live" {
    linux /vmlinuz boot=live quiet nomodeset
    initrd /initrd
}
EOF

Create a special file in image 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/image/DEBIAN_CUSTOM

Your LIVE_BOOT directory should now roughly look like this.

LIVE_BOOT/chroot/*tons of chroot files*
LIVE_BOOT/scratch/grub.cfg
LIVE_BOOT/image/DEBIAN_CUSTOM
LIVE_BOOT/image/initrd
LIVE_BOOT/image/vmlinuz
LIVE_BOOT/image/live/filesystem.squashfs

Create a bootable medium

Please note that there are two separate sets of instructions below for creating a bootable medium for the live environment. One process is titled Create Bootable ISO/CD and the other, separate process, is titled Create Bootable USB.

  • The Create Bootable ISO/CD instructions will result in an .iso image file containing our live environment.
  • The Create Bootable USB instructions will result in our live environment being installed directly to a USB device.

The .iso file we create with the Create Bootable ISO/CD instructions can be burned to a CD-ROM (optical media), or written to a USB device with dd. The functionality that allows this “dd-able” behavior from our .iso file does not come for free. The process is 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. Read more in my Notes which details my discoveries.

Create Bootable ISO/CD

Install the live environment to an .iso file which can be burned to optical media.

As stated above, the .iso file generated by these steps can be written to a USB device with something like dd.

I have separated the instructions for creating a BIOS bootable, UEFI bootable, or combination BIOS + UEFI bootable .iso in order to illustrate the separate processes. Click either “BIOS”, “UEFI”, or “BIOS + UEFI” and follow the instructions that are appropriate for your needs.

BIOS Support Only

Create a grub BIOS image.

grub-mkstandalone \
    --format=i386-pc \
    --output=$HOME/LIVE_BOOT/scratch/core.img \
    --install-modules="linux normal iso9660 biosdisk memdisk search tar ls" \
    --modules="linux normal iso9660 biosdisk search" \
    --locales="" \
    --fonts="" \
    "boot/grub/grub.cfg=$HOME/LIVE_BOOT/scratch/grub.cfg"

Use cat to combine a bootable Grub cdboot.img bootloader with our boot image.

cat \
    /usr/lib/grub/i386-pc/cdboot.img \
    $HOME/LIVE_BOOT/scratch/core.img \
> $HOME/LIVE_BOOT/scratch/bios.img

Generate the ISO file.

xorriso \
    -as mkisofs \
    -iso-level 3 \
    -full-iso9660-filenames \
    -volid "DEBIAN_CUSTOM" \
    --grub2-boot-info \
    --grub2-mbr /usr/lib/grub/i386-pc/boot_hybrid.img \
    -eltorito-boot \
        boot/grub/bios.img \
        -no-emul-boot \
        -boot-load-size 4 \
        -boot-info-table \
        --eltorito-catalog boot/grub/boot.cat \
    -output "${HOME}/LIVE_BOOT/debian-custom.iso" \
    -graft-points \
        "${HOME}/LIVE_BOOT/image" \
        /boot/grub/bios.img=$HOME/LIVE_BOOT/scratch/bios.img

Now burn the ISO to a CD and you should be ready to boot from it using a BIOS system.

UEFI Support Only

Create a grub UEFI image.

grub-mkstandalone \
    --format=x86_64-efi \
    --output=$HOME/LIVE_BOOT/scratch/bootx64.efi \
    --locales="" \
    --fonts="" \
    "boot/grub/grub.cfg=$HOME/LIVE_BOOT/scratch/grub.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/scratch && \
    dd if=/dev/zero of=efiboot.img bs=1M count=10 && \
    mkfs.vfat efiboot.img && \
    mmd -i efiboot.img efi efi/boot && \
    mcopy -i efiboot.img ./bootx64.efi ::efi/boot/
)

Generate the ISO file.

xorriso \
    -as mkisofs \
    -iso-level 3 \
    -full-iso9660-filenames \
    -volid "DEBIAN_CUSTOM" \
    -eltorito-alt-boot \
        -e EFI/efiboot.img \
        -no-emul-boot \
    -append_partition 2 0xef ${HOME}/LIVE_BOOT/scratch/efiboot.img \
    -output "${HOME}/LIVE_BOOT/debian-custom.iso" \
    -graft-points \
        "${HOME}/LIVE_BOOT/image" \
        /EFI/efiboot.img=$HOME/LIVE_BOOT/scratch/efiboot.img

Now burn the ISO to a CD and you should be ready to boot from it using a UEFI system.

BIOS + UEFI Support

Create a grub UEFI image.

grub-mkstandalone \
    --format=x86_64-efi \
    --output=$HOME/LIVE_BOOT/scratch/bootx64.efi \
    --locales="" \
    --fonts="" \
    "boot/grub/grub.cfg=$HOME/LIVE_BOOT/scratch/grub.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/scratch && \
    dd if=/dev/zero of=efiboot.img bs=1M count=10 && \
    mkfs.vfat efiboot.img && \
    mmd -i efiboot.img efi efi/boot && \
    mcopy -i efiboot.img ./bootx64.efi ::efi/boot/
)

Create a grub BIOS image.

grub-mkstandalone \
    --format=i386-pc \
    --output=$HOME/LIVE_BOOT/scratch/core.img \
    --install-modules="linux normal iso9660 biosdisk memdisk search tar ls" \
    --modules="linux normal iso9660 biosdisk search" \
    --locales="" \
    --fonts="" \
    "boot/grub/grub.cfg=$HOME/LIVE_BOOT/scratch/grub.cfg"

Use cat to combine a bootable Grub cdboot.img bootloader with our boot image.

cat \
    /usr/lib/grub/i386-pc/cdboot.img \
    $HOME/LIVE_BOOT/scratch/core.img \
> $HOME/LIVE_BOOT/scratch/bios.img

Generate the ISO file.

xorriso \
    -as mkisofs \
    -iso-level 3 \
    -full-iso9660-filenames \
    -volid "DEBIAN_CUSTOM" \
    -eltorito-boot \
        boot/grub/bios.img \
        -no-emul-boot \
        -boot-load-size 4 \
        -boot-info-table \
        --eltorito-catalog boot/grub/boot.cat \
    --grub2-boot-info \
    --grub2-mbr /usr/lib/grub/i386-pc/boot_hybrid.img \
    -eltorito-alt-boot \
        -e EFI/efiboot.img \
        -no-emul-boot \
    -append_partition 2 0xef ${HOME}/LIVE_BOOT/scratch/efiboot.img \
    -output "${HOME}/LIVE_BOOT/debian-custom.iso" \
    -graft-points \
        "${HOME}/LIVE_BOOT/image" \
        /boot/grub/bios.img=$HOME/LIVE_BOOT/scratch/bios.img \
        /EFI/efiboot.img=$HOME/LIVE_BOOT/scratch/efiboot.img

Now burn the ISO to a CD and you should be ready to boot from it using either a UEFI or a BIOS system.

Create Bootable USB

Install the live environment to a USB device.

As stated above, installing a live environment to a USB device is not the same as writing an .iso file to a USB device. The end result is the same for the most part in both those scenarios, but there are subtle differences worth understanding, and there are valid reasons someone may want to install a live environment directly to a USB device rather than writing an .iso file to a USB device.

I have separated the instructions for creating a BIOS bootable, UEFI bootable, or combination BIOS + UEFI bootable USB device in order to illustrate the separate processes. Click either “BIOS”, “UEFI”, or “BIOS + UEFI” and follow the instructions that are appropriate for your needs.

BIOS Support Only

I am assuming you have an umounted blank USB drive at /dev/sdz. To allow easily swapping in a real block device, I am using a variable named $disk in these commands.

Export the disk variable.

export disk=/dev/sdz

Create a mount point for the USB drive.

sudo mkdir -p /mnt/usb

Partition the USB drive using parted. This command creates 1 partition in a traditional MBR layout.

sudo parted --script $disk \
    mklabel msdos \
    mkpart primary fat32 1MiB 100%

Format the partition.

sudo mkfs.vfat -F32 ${disk}1

Mount the partition.

sudo mount ${disk}1 /mnt/usb

Install Grub for i386-pc booting.

sudo grub-install \
    --target=i386-pc \
    --boot-directory=/mnt/usb/boot \
    --recheck \
    $disk

Create a live directory on the USB device.

sudo mkdir -p /mnt/usb/{boot/grub,live}

Copy the Debian live environment files we previously generated to the USB disk.

sudo cp -r $HOME/LIVE_BOOT/image/* /mnt/usb/

Copy the grub.cfg boot configuration to the USB device.

sudo cp \
    $HOME/LIVE_BOOT/scratch/grub.cfg \
    /mnt/usb/boot/grub/grub.cfg

Now unmount the disk and you should be ready to boot from it on a BIOS system.

sudo umount /mnt/usb

UEFI Support Only

I am assuming you have an umounted blank USB drive at /dev/sdz. To allow easily swapping in a real block device, I am using a variable named $disk in these commands.

Export the disk variable.

export disk=/dev/sdz

Create some mount points for the USB drive.

sudo mkdir -p /mnt/{usb,efi}

Partition the USB drive using parted. This command creates 2 partitions in a GPT (Guid Partition Table) layout. One partition for UEFI, and one for our Debian OS and other live data.

sudo parted --script $disk \
    mklabel gpt \
    mkpart ESP fat32 1MiB 200MiB \
        name 1 EFI \
        set 1 esp on \
    mkpart primary fat32 200MiB 100% \
        name 2 LINUX \
        set 2 msftdata on

Format the UEFI and data partitions.

sudo mkfs.vfat -F32 ${disk}1 && \
sudo mkfs.vfat -F32 ${disk}2

Mount the partitions.

sudo mount ${disk}1 /mnt/efi && \
sudo mount ${disk}2 /mnt/usb

Install Grub for x86_64 UEFI booting.

sudo grub-install \
    --target=x86_64-efi \
    --efi-directory=/mnt/efi \
    --boot-directory=/mnt/usb/boot \
    --removable \
    --recheck

Create a live directory on the USB device.

sudo mkdir -p /mnt/usb/{boot/grub,live}

Copy the Debian live environment files we previously generated to the USB disk.

sudo cp -r $HOME/LIVE_BOOT/image/* /mnt/usb/

Copy the grub.cfg boot configuration to the USB device.

sudo cp \
    $HOME/LIVE_BOOT/scratch/grub.cfg \
    /mnt/usb/boot/grub/grub.cfg

Now unmount the disk and you should be ready to boot from it on a UEFI system.

sudo umount /mnt/{usb,efi}

BIOS + UEFI Support

I am assuming you have an umounted blank USB drive at /dev/sdz. To allow easily swapping in a real block device, I am using a variable named $disk in these commands.

Export the disk variable.

export disk=/dev/sdz

Create some mount points for the USB drive.

sudo mkdir -p /mnt/{usb,efi}

Partition the USB drive using parted. This command creates 3 partitions in a GPT (Guid Partition Table) layout. One partition for the BIOS boot record, one for UEFI, and one for our Debian OS and other live data.

sudo parted --script $disk \
    mklabel gpt \
    mkpart primary fat32 2048s 4095s \
        name 1 BIOS \
        set 1 bios_grub on \
    mkpart ESP fat32 4096s 413695s \
        name 2 EFI \
        set 2 esp on \
    mkpart primary fat32 413696s 100% \
        name 3 LINUX \
        set 3 msftdata on

Generate a hybrid MBR for the USB device. Note, this is non-standard and so may not work on all systems. The only guides I’ve found on hybrid MBRs show that it must be done with gdisk. gdisk supports commands not in sgdisk, so this command is not easily scriptable. The documentation for gdisk warn that this procedure is non-standard, flaky, and unsupported, but this does generally seem to do what is expected. It allows for both BIOS and UEFI booting from the same USB device.

sudo gdisk $disk << EOF
r     # recovery and transformation options
h     # make hybrid MBR
1 2 3 # partition numbers for hybrid MBR
N     # do not place EFI GPT (0xEE) partition first in MBR
EF    # MBR hex code
N     # do not set bootable flag
EF    # MBR hex code
N     # do not set bootable flag
83    # MBR hex code
Y     # set the bootable flag
x     # extra functionality menu
h     # recompute CHS values in protective/hybrid MBR
w     # write table to disk and exit
Y     # confirm changes
EOF

Format the UEFI and data partitions.

sudo mkfs.vfat -F32 ${disk}2 && \
sudo mkfs.vfat -F32 ${disk}3

Mount the partitions.

sudo mount ${disk}2 /mnt/efi && \
sudo mount ${disk}3 /mnt/usb

Install Grub for x86_64 UEFI booting.

sudo grub-install \
    --target=x86_64-efi \
    --efi-directory=/mnt/efi \
    --boot-directory=/mnt/usb/boot \
    --removable \
    --recheck

Install Grub for i386-pc booting.

sudo grub-install \
    --target=i386-pc \
    --boot-directory=/mnt/usb/boot \
    --recheck \
    $disk

Create a live directory on the USB device.

sudo mkdir -p /mnt/usb/{boot/grub,live}

Copy the Debian live environment files we previously generated to the USB disk.

sudo cp -r $HOME/LIVE_BOOT/image/* /mnt/usb/

Copy the grub.cfg boot configuration to the USB device.

sudo cp \
    $HOME/LIVE_BOOT/scratch/grub.cfg \
    /mnt/usb/boot/grub/grub.cfg

Now unmount the disk and you should be ready to boot from it on either a BIOS or UEFI system.

sudo umount /mnt/{usb,efi}

Citations