This procedure shows how to create a bootable and installable Ubuntu Live (along with the automatic hardware detection and configuration) from scratch. The steps described below are also available in this repo in the /scripts directory.

Authors
Marcos Vallim - Founder, Author, Development, Test, Documentation - mvallim
Ken Gilmer - Commiter, Development, Test, Documentation - kgilmer
See also the list of contributors who participated in this project.

Ways of Using this Tutorial
(Recommended) follow the directions step by step below to understand how to build an Ubuntu ISO.
Run the build.sh script in the scripts directory after checking this repo out locally.
Fork this repo and run the github action build. This will generate an ISO in your github account.
build-bionic build-focal

Terms
build system - the computer environment running the build scripts that generate the ISO.
live system - the computer environment that runs from the live OS, generated by a build system. This may also be referred to as the chroot environment.
target system - the computer environment that runs after installation has completed from a live system.
Prerequisites (GNU/Linux Debian/Ubuntu)
Install packages we need in the build system required by our scripts.

sudo apt-get install \
    binutils \
    debootstrap \
    squashfs-tools \
    xorriso \
    grub-pc-bin \
    grub-efi-amd64-bin \
    mtools
mkdir $HOME/live-ubuntu-from-scratch
Bootstrap and Configure Ubuntu
debootstrap is a program for generating OS images. We install it into our build system to begin generating our ISO.

Checkout bootstrap

sudo debootstrap \
   --arch=amd64 \
   --variant=minbase \
   focal \
   $HOME/live-ubuntu-from-scratch/chroot \
   http://us.archive.ubuntu.com/ubuntu/
debootstrap is used to create a Debian base system from scratch, without requiring the availability of dpkg or apt. It does this by downloading .deb files from a mirror site, and carefully unpacking them into a directory which can eventually be chrooted into.

Configure external mount points

sudo mount --bind /dev $HOME/live-ubuntu-from-scratch/chroot/dev
  
sudo mount --bind /run $HOME/live-ubuntu-from-scratch/chroot/run
As we will be updating and installing packages (grub among them), these mount points are necessary inside the chroot environment, so we are able to finish the installation without errors.

Define chroot environment
A chroot on Unix operating systems is an operation that changes the apparent root directory for the current running process and its children. A program that is run in such a modified environment cannot name (and therefore normally cannot access) files outside the designated directory tree. The term “chroot” may refer to the chroot system call or the chroot wrapper program. The modified environment is called a chroot jail.

Reference: https://en.wikipedia.org/wiki/Chroot

From this point we will be configuring the live system.

Access chroot environment

sudo chroot $HOME/live-ubuntu-from-scratch/chroot
Configure mount points, home and locale

mount none -t proc /proc

mount none -t sysfs /sys

mount none -t devpts /dev/pts

export HOME=/root

export LC_ALL=C
These mount points are necessary inside the chroot environment, so we are able to finish the installation without errors.

Set a custom hostname

echo "ubuntu-fs-live" > /etc/hostname
Configure apt sources.list

cat <<EOF > /etc/apt/sources.list
deb http://us.archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse
deb-src http://us.archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse

deb http://us.archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse
deb-src http://us.archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse

deb http://us.archive.ubuntu.com/ubuntu/ focal-updates main restricted universe multiverse
deb-src http://us.archive.ubuntu.com/ubuntu/ focal-updates main restricted universe multiverse
EOF
Update indexes packages

apt-get update
Install systemd

apt-get install -y libterm-readline-gnu-perl systemd-sysv
systemd is a system and service manager for Linux. It provides aggressive parallelization capabilities, uses socket and D-Bus activation for starting services, offers on-demand starting of daemons, keeps track of processes using Linux control groups, maintains mount and automount points and implements an elaborate transactional dependency-based service control logic.

Configure machine-id and divert

dbus-uuidgen > /etc/machine-id

ln -fs /etc/machine-id /var/lib/dbus/machine-id
The /etc/machine-id file contains the unique machine ID of the local system that is set during installation or boot. The machine ID is a single newline-terminated, hexadecimal, 32-character, lowercase ID. When decoded from hexadecimal, this corresponds to a 16-byte/128-bit value. This ID may not be all zeros.

dpkg-divert --local --rename --add /sbin/initctl

ln -s /bin/true /sbin/initctl
dpkg-divert is the utility used to set up and update the list of diversions.

Upgrade packages

apt-get -y upgrade