This guide adds two new options to the GRUB menu for safely launching live modes. You will get two ways to launch dom0 in RAM for protection against forensics: 1. Qubes Overlay-Live Mode 2. Qubes Zram-Live Mode
🛡️ Both modes significantly increase dom0 security - the default root runs in read‑only mode, and operations take place within a hardened copy of the system. This also works great for experiments in Qubes or for beginners who want to learn without fear of breaking anything - all changes disappear after a reboot. It will extend the lifespan of your SSD. > Overlay‑Live Mode is very fast and maximally (paranoid) secure. Zram‑Live Mode starts slowly, uses more CPU power, but saves RAM dramatically (~ 2× compared to Overlay‑Live).
This guide solves the old problem of implement live boot by porting grub-live to Qubes - amnesia / non-persistent boot / anti-forensics.
You will need: at least 16 GB RAM for Zram-Live Mode and at least 24 GB RAM for Overlay-Live Mode for comfortably launching several qubes in live mode.
> 📢 Don’t worry about installing these modes - doing so won’t affect the default Qubes boot. I created this scenario to be as safe as possible and isolated from the default Qubes boot. The new GRUB options are added to /etc/grub.d/40_custom (so it don’t modify your /etc/default/grub), and the new dracut modules only run when the additional GRUB parameters are supplied (these parameters aren’t present in the default boot). Default Qubes boot won’t be affected even if the new modes encounter problems in the future, such as after dom0 updates.
But Make a backup before you start working 🙂
:green_circle: If you’re worried about entering something incorrectly, use the fully automated live‑mode setup via a simple script
Step 1. Make New Directorys for Dracut Automation Modules:
sudo mkdir /usr/lib/dracut/modules.d/90ramboot
sudo mkdir /usr/lib/dracut/modules.d/90overlayfs-root
Step 2. Make Two Dracut Script Files module-setup.sh:
sudo touch /usr/lib/dracut/modules.d/90ramboot/module-setup.sh
chmod 755 /usr/lib/dracut/modules.d/90ramboot/module-setup.sh
sudo nano /usr/lib/dracut/modules.d/90ramboot/module-setup.sh
add:
#!/usr/bin/bash
check() {
return 0
}
depends() {
return 0
}
install() {
inst_simple "$moddir/zram-mount.sh"
inst_hook cleanup 00 "$moddir/zram-mount.sh"
}
Press Ctrl + O to save file.
Press Ctrl + X to exit nano editor.
sudo touch /usr/lib/dracut/modules.d/90overlayfs-root/module-setup.sh
chmod 755 /usr/lib/dracut/modules.d/90overlayfs-root/module-setup.sh
sudo nano /usr/lib/dracut/modules.d/90overlayfs-root/module-setup.sh
add:
#!/bin/bash
check() {
# do not add modules if the kernel does not have overlayfs support
[ -d /lib/modules/$kernel/kernel/fs/overlayfs ] || return 1
}
depends() {
# We do not depend on any modules - just some root
return 0
}
# called by dracut
installkernel() {
hostonly='' instmods overlay
}
install() {
inst_hook pre-pivot 10 "$moddir/overlay-mount.sh"
}
Ctrl + O to save file.
Press Ctrl + X to exit nano editor.
Step 3. Make New Dracut Script File overlay-mount.sh:
sudo touch /usr/lib/dracut/modules.d/90overlayfs-root/overlay-mount.sh
chmod 755 /usr/lib/dracut/modules.d/90overlayfs-root/overlay-mount.sh
sudo nano /usr/lib/dracut/modules.d/90overlayfs-root/overlay-mount.sh
add:
#!/bin/sh
. /lib/dracut-lib.sh
if ! getargbool 0 rootovl ; then
return
fi
modprobe overlay
mount -o remount,nolock,noatime $NEWROOT
mkdir -p /live/image
mount --bind $NEWROOT /live/image
umount $NEWROOT
mkdir /cow
mount -n -t tmpfs -o mode=0755,size=70%,nr_inodes=500k,noexec,nodev,nosuid,noatime,nodiratime tmpfs /cow
mkdir /cow/work /cow/rw
mount -t overlay -o noatime,nodiratime,volatile,lowerdir=/live/image,upperdir=/cow/rw,workdir=/cow/work,default_permissions,relatime overlay $NEWROOT
mkdir -p $NEWROOT/live/cow
mkdir -p $NEWROOT/live/image
mount --bind /cow/rw $NEWROOT/live/cow
umount /cow
mount --bind /live/image $NEWROOT/live/image
umount /live/image
Ctrl + O to save file.
Press Ctrl + X to exit nano editor.
Step 4. Creating a script to automatically create zram-mount.sh and edit /etc/grub.d/40_custom.
sudo touch /usr/local/bin/grub-custom.sh
sudo chmod 755 /usr/local/bin/grub-custom.sh
sudo nano /usr/local/bin/grub-custom.sh
Add:
#!/bin/bash
#BOOT_UUID
BOOT_UUID=$(findmnt -n -o UUID /boot 2>/dev/null || echo "AUTO_BOOT_NOT_FOUND")
if [ "$BOOT_UUID" = "AUTO_BOOT_NOT_FOUND" ]; then
BOOT_UUID=$(blkid -s UUID -o value -d $(findmnt -n -o SOURCE /boot 2>/dev/null))
fi
# LUKS_UUID
LUKS_DEVICE=$(blkid -t TYPE="crypto_LUKS" -o device 2>/dev/null | head -n1 || echo "")
if [ -n "$LUKS_DEVICE" ]; then
LUKS_UUID=$(sudo cryptsetup luksUUID "$LUKS_DEVICE" 2>/dev/null)
else
LUKS_UUID="AUTO_LUKS_NOT_FOUND"
fi
# Latest XEN_PATH
XEN_PATH=$(ls /boot/xen*.gz 2>/dev/null | sort -V | tail -1 | xargs basename 2>/dev/null || echo "/xen-4.19.4.gz")
# Latest kernel/initramfs
LATEST_KERNEL=$(ls /boot/vmlinuz-*qubes*.x86_64 2>/dev/null | grep -E 'qubes\.fc[0-9]+' | sort -V | tail -1 | xargs basename)
LATEST_INITRAMFS=$(echo "/initramfs-${LATEST_KERNEL#vmlinuz-}.img")
# Max memory dom0
DOM0_MAX_KB=$(xenstore-read /local/domain/0/memory/hotplug-max 2>/dev/null \
|| xenstore-read /local/domain/0/memory/static-max 2>/dev/null \
|| echo 0)
if [ "$DOM0_MAX_KB" -gt 0 ]; then
DOM0_MAX_MB=$(( (DOM0_MAX_KB * 70) / (1024 * 100) ))
DOM0_MAX_GB=$(( DOM0_MAX_MB / 1024 ))
DOM0_MAX_GBG="${DOM0_MAX_GB}G"
DOM0_MAX_RAM="dom0_mem=max:${DOM0_MAX_MB}M"
else
DOM0_MAX_RAM="dom0_mem=max:10240M"
DOM0_MAX_GB="10"
fi
cat > /usr/lib/dracut/modules.d/90ramboot/zram-mount.sh << EOF
#!/bin/sh
. /lib/dracut-lib.sh
if ! getargbool 0 rootzram ; then
return
fi
mkdir /mnt
umount /sysroot
mount -o ro /dev/mapper/qubes_dom0-root /mnt
modprobe zram
echo $DOM0_MAX_GBG > /sys/block/zram0/disksize
/mnt/usr/sbin/mkfs.ext2 /dev/zram0
mount -o nodev,nosuid,noatime,nodiratime /dev/zram0 /sysroot
cp -a /mnt/* /sysroot
exit 0
EOF
chmod 755 /usr/lib/dracut/modules.d/90ramboot/zram-mount.sh
cat > /etc/dracut.conf.d/ramboot.conf << 'EOF'
add_drivers+=" zram "
add_dracutmodules+=" ramboot "
EOF
# Update INITRAMFS
dracut --verbose --force
cat > /etc/grub.d/40_custom << EOF
#!/usr/bin/sh
exec tail -n +3 \$0
menuentry 'Qubes Overlay-Live Mode (latest kernel)' --class qubes --class gnu-linux --class gnu --class os --class xen \$menuentry_id_option 'xen-gnulinux-simple-/dev/mapper/qubes_dom0-root' {
insmod part_gpt
insmod ext2
search --no-floppy --fs-uuid --set=root $BOOT_UUID
echo 'Loading Xen ...'
if [ "\$grub_platform" = "pc" -o "\$grub_platform" = "" ]; then
xen_rm_opts=
else
xen_rm_opts="no-real-mode edd=off"
fi
insmod multiboot2
multiboot2 /$XEN_PATH placeholder console=none dom0_mem=min:1024M $DOM0_MAX_RAM ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096 \${xen_rm_opts}
echo 'Loading Linux $LATEST_KERNEL ...'
module2 /$LATEST_KERNEL placeholder root=/dev/mapper/qubes_dom0-root ro rd.luks.uuid=$LUKS_UUID rd.lvm.lv=qubes_dom0/root rd.lvm.lv=qubes_dom0/swap plymouth.ignore-serial-consoles rhgb rootovl quiet usbcore.authorized_default=0
echo 'Loading initial ramdisk ...'
insmod multiboot2
module2 --nounzip $LATEST_INITRAMFS
}
menuentry 'Qubes Zram-Live Mode (latest kernel)' --class qubes --class gnu-linux --class gnu --class os --class xen \$menuentry_id_option 'xen-gnulinux-simple-/dev/mapper/qubes_dom0-root' {
insmod part_gpt
insmod ext2
search --no-floppy --fs-uuid --set=root $BOOT_UUID
echo 'Loading Xen ...'
if [ "\$grub_platform" = "pc" -o "\$grub_platform" = "" ]; then
xen_rm_opts=
else
xen_rm_opts="no-real-mode edd=off"
fi
insmod multiboot2
multiboot2 /$XEN_PATH placeholder console=none dom0_mem=min:1024M $DOM0_MAX_RAM ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096 \${xen_rm_opts}
echo 'Loading Linux $LATEST_KERNEL ...'
module2 /$LATEST_KERNEL placeholder root=/dev/mapper/qubes_dom0-root ro rd.luks.uuid=$LUKS_UUID rd.lvm.lv=qubes_dom0/root rd.lvm.lv=qubes_dom0/swap plymouth.ignore-serial-consoles rhgb rootzram quiet usbcore.authorized_default=0
echo 'Loading initial ramdisk ...'
insmod multiboot2
module2 --nounzip $LATEST_INITRAMFS
}
EOF
# Update GRUB
grub2-mkconfig -o /boot/grub2/grub.cfg
# Disable Dom0 Swap:
sed -i '/[[:space:]]\+swap[[:space:]]\+/s/^/#/' "/etc/fstab"
swapoff -a
Press Ctrl + O to save file.
Press Ctrl + X to exit nano editor.
*Run* grub-custom.sh**
sudo /usr/local/bin/grub-custom.sh
> If you want to add your custom GRUB parameters for live modes, do so in /etc/grub.d/40_custom
Step 5. Clone dangerous qubes (dvm‑template, appVMs) into a pool in dom0
> ❗️Since only dom0 runs in live mode, you have to start the VMs from the pool in dom0. You can use the default pool varlibqubes or create a new pool.
In the Qube Manager click clone qube, then in Advanced select a pool in dom0 (varlibqubes or your new pool. not vm‑pool). If you have a lot of memory, you can run all appVMs (sys, dvm, appVM) in live mode.
> ❗️Don't add templates to varlibqubes - templates don't retain any session data in an appVM (that's the point of Qubes' isolation). So, in live mode it's sufficient that only the private and volatile storages are active. You can inspect all template metadata yourself and verify that none of them contain artifacts from sessions in appVM.
Restart Qubes OS and Test Qubes live modes 😉
> Zram-Live Mode takes longer to start (about 30–40 seconds more).
> ❗️ You can update templates in live modes, but update dom0 in persistent mode! .
>
> Now if dom0 updates kernels or If you’ve changed dom_mem=max in GRUB, just run sudo /usr/local/bin/grub-custom.sh and the live modes will work with the new kernels, and max memory settings.
> Remember that the data will be erased only after a reboot of Qubes OS (like Tails). > If you create a qube in the vm‑pool while in live mode, this qube won’t be saved in the Qube Manager after a reboot. > You can make backups in live mode (I’ve done it many times using a Overlay-Live Mode).
🔦
You can add a “System Monitor” widget to the XFCE panel and configure it to run command findmnt -n -o SOURCE /. This widget will display which mode you’re currently in:
You can also use this terminal theme so can see which mode you’re currently in:
Click CTRL + H in thunar of dom0 and add this code into .bashrc instead of the default code:
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# User specific environment
if ! [[ "$PATH" =~ "$HOME/.local/bin:$HOME/bin:" ]]
then
PATH="$HOME/.local/bin:$HOME/bin:$PATH"
fi
export PATH
###########################
export VIRTUAL_ENV_DISABLE_PROMPT=true
__qubes_update_prompt_data() {
local RETVAL=$?
__qubes_venv=''
[[ -n "$VIRTUAL_ENV" ]] && __qubes_venv=$(basename "$VIRTUAL_ENV")
__qubes_git=''
__qubes_git_color=$(tput setaf 10) # clean
local git_branch=$(git --no-optional-locks rev-parse --abbrev-ref HEAD 2> /dev/null)
if [[ -n "$git_branch" ]]; then
local git_status=$(git --no-optional-locks status --porcelain 2> /dev/null | tail -n 1)
[[ -n "$git_status" ]] && __qubes_git_color=$(tput setaf 11) # dirty
__qubes_git="‹${git_branch}›"
fi
__qubes_prompt_symbol_color=$(tput sgr0)
[[ "$RETVAL" -ne 0 ]] && __qubes_prompt_symbol_color=$(tput setaf 1)
return $RETVAL # to preserve retcode
}
if [[ -n "$git_branch" ]]; then
PROMPT_COMMAND="$PROMPT_COMMAND; __qubes_update_prompt_data"
else
PROMPT_COMMAND="__qubes_update_prompt_data"
fi
PS1=''
PS1+='\[$(tput setaf 7)\]$(echo -ne $__qubes_venv)\[$(tput sgr0)\]'
PS1+='\[$(tput setaf 14)\]\u'
PS1+='\[$(tput setaf 15)\] 👑 '
PS1+='\[$(tput setaf 9)\]\h'
PS1+=" $(findmnt -n -o SOURCE /)"
PS1+='\[$(tput setaf 15)\]:'
PS1+='\[$(tput setaf 7)\]\w '
PS1+='\[$(echo -ne $__qubes_git_color)\]$(echo -ne $__qubes_git)\[$(tput sgr0)\] '
PS1+='\[$(tput setaf 8)\]\[$([[ -n "$QUBES_THEME_SHOW_TIME" ]] && echo -n "[\t]")\]\[$(tput sgr0)\]'
PS1+='\[$(tput sgr0)\]\n'
PS1+='\[$(echo -ne $__qubes_prompt_symbol_color)\]\$\[$(tput sgr0)\] '
If you have limited memory, use this guide: https://forum.qubes-os.org/t/really-disposable-ram-based-qubes/21532
😎 Also use these guides to hide traces of VPN/Tor connections and for external system shutdown and memory clearing for maximum paranoid security: Installation of AmneziaVPN: effective circumvention of internet blocks via DPI USB Kill Switch for Qubes OS