These are steps that I used on an Ubuntu 22.04 LTS (Jammy Jellyfish) 64-bit Normal installation system to build an amd64 (64-bit) Debian 11 (Bullseye) 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.

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 Live Manual 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 \
    grub-efi-ia32-bin \
    mtools \
    dosfstools

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 bullseye 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 \
    bullseye \
    $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] Install a Linux kernel of your choosing. 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
insmod part_gpt
insmod part_msdos
insmod fat
insmod iso9660

insmod all_video
insmod font

set default="0"
set timeout=30

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

menuentry "Debian Live [EFI/GRUB]" {
    search --no-floppy --set=root --label DEBLIVE
    linux ($root)/live/vmlinuz boot=live
    initrd ($root)/live/initrd
}

menuentry "Debian Live [EFI/GRUB] (nomodeset)" {
    search --no-floppy --set=root --label DEBLIVE
    linux ($root)/live/vmlinuz boot=live nomodeset
    initrd ($root)/live/initrd
}
EOF

Copy the grub.cfg file to the EFI BOOT directory.

cp $HOME/LIVE_BOOT/staging/boot/grub/grub.cfg $HOME/LIVE_BOOT/staging/EFI/BOOT/

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-embed.cfg
if ! [ -d "$cmdpath" ]; then
    # On some firmware, GRUB has a wrong cmdpath when booted from an optical disc.
    # https://gitlab.archlinux.org/archlinux/archiso/-/issues/183
    if regexp --set=1:isodevice '^(\([^)]+\))\/?[Ee][Ff][Ii]\/[Bb][Oo][Oo][Tt]\/?$' "$cmdpath"; then
        cmdpath="${isodevice}/EFI/BOOT"
    fi
fi
configfile "${cmdpath}/grub.cfg"
EOF

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

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 -O i386-efi \
    --modules="part_gpt part_msdos fat iso9660" \
    --locales="" \
    --themes="" \
    --fonts="" \
    --output="$HOME/LIVE_BOOT/staging/EFI/BOOT/BOOTIA32.EFI" \
    "boot/grub/grub.cfg=$HOME/LIVE_BOOT/tmp/grub-embed.cfg"
grub-mkstandalone -O x86_64-efi \
    --modules="part_gpt part_msdos fat iso9660" \
    --locales="" \
    --themes="" \
    --fonts="" \
    --output="$HOME/LIVE_BOOT/staging/EFI/BOOT/BOOTx64.EFI" \
    "boot/grub/grub.cfg=$HOME/LIVE_BOOT/tmp/grub-embed.cfg"

Create a FAT16 UEFI boot disk image containing the EFI bootloaders.

(cd $HOME/LIVE_BOOT/staging && \
    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/staging/EFI/BOOT/BOOTIA32.EFI \
        $HOME/LIVE_BOOT/staging/EFI/BOOT/BOOTx64.EFI \
        $HOME/LIVE_BOOT/staging/boot/grub/grub.cfg \
        ::/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 "DEBLIVE" \
    --mbr-force-bootable -partition_offset 16 \
    -joliet -joliet-long -rational-rock \
    -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 --interval:appended_partition_2:all:: \
        -no-emul-boot \
        -isohybrid-gpt-basdat \
    -append_partition 2 C12A7328-F81F-11D2-BA4B-00A0C93EC93B ${HOME}/LIVE_BOOT/staging/efiboot.img \
    "${HOME}/LIVE_BOOT/staging"

Citations