This guide combines the best practices from this topic https://forum.qubes-os.org/t/qubes-in-tmpfs/11127 and it adds new options and capabilities for launching Qubes in RAM. (The old topic’s founder and one of its main contributors are no longer active on forum)

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

g|689x434

This guide solves the old problem of implement live boot by porting grub-live to Qubes - amnesia / non-persistent boot / anti-forensics. 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 (used by native kicksecure/Whonix) is more secure and fast:

> Root filesystem is effectively read‑only. > The system runs on an overlay: the original root is mounted as lowerdir=/live/image (read‑only view), and all changes go into upperdir=/cow/rw on tmpfs. This makes it hard for malware to persist on the real disk, since any modifications live only in the volatile upper layer. Overlay in tmpfs does not create and format new block devices nor copy the entire root filesystem at runtime. There are fewer points where an attacker could hook into disk‑level operations or tamper with filesystem creation and bulk copying.

Overlay is much closer to a real Tails OS (a read‑only image). I added some additional flags to overlay‑tmpfs to strengthen the security of the live mode. > Zram‑Live Mode works by copying (cp -a) root to zram0 - it is a significantly less secure and slower scenario. But it saves RAM usage dramatically (~ 2x Overlay-Live Mode).

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.

Make a backup before you start working!

+ Step 1. Disable Dom0 Swap:

sudo nano /etc/fstab

Using nano, type a # character in front of the line with word “swap” in the middle (usually the last line). Like this: #UUID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx swap defaults,x-systemd.device-timeout=0 0 0

Press Ctrl + O to save file. Press Ctrl + X to exit nano editor.

That will permanently disable your Dom0 Swap after restart, which is likely best for operating Qubes Dom0 in live mode.

sudo swapoff -a

+ Step 2. Edit GRUB:

sudo nano /etc/default/grub

Edit line GRUB_TIMEOUT=5 to GRUB_TIMEOUT=30 (change 5 seconds to 30 seconds). Within the GRUB_CMDLINE_XEN_DEFAULT line, make a partial modification of this line, from dom0_mem=max:4096M to now be dom0_mem=max:10240M → Just change the text “4096M” to “10240M” (without quotes).

Press Ctrl + O to save grub file. Press Ctrl + X to exit nano editor.

> ❗️ If you have something like 32GB of RAM or greater in your system, and you want to run several fully stateless user qubes within Dom0 RAM space, then you may want to consider increasing this “10240M” Dom0 RAM maximum value, in order to allow for more Dom0 RAM space where you can store more user qubes as fully stateless. For example, with 32GB of system RAM, you may choose to allocate “20480M” (20GB) or even greater if you wish. *!! Important:* If you set more than 10 GB of dom0_mem=max, also change ram-disk size in Step 6 line** echo 10G > /sys/block/zram0/disksize

+ Step 3. Make New Directorys for Dracut Automation Modules:

sudo mkdir /usr/lib/dracut/modules.d/01ramboot sudo mkdir /usr/lib/dracut/modules.d/90overlayfs-root

+ Step 4. Make Two Dracut Script Files module-setup.sh:

sudo touch /usr/lib/dracut/modules.d/01ramboot/module-setup.sh sudo nano /usr/lib/dracut/modules.d/01ramboot/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 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"
}
Press Ctrl + O to save file. Press Ctrl + X to exit nano editor.

+ Step 5. Make New Dracut Script File overlay-mount.sh:

sudo touch /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=95%,nr_inodes=500k,noexec,nodev,nosuid,relatime tmpfs /cow
mkdir /cow/work /cow/rw
mount -t overlay -o noatime,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
Press Ctrl + O to save file. Press Ctrl + X to exit nano editor.

+ Step 6. Make New Dracut Script File zram-mount.sh:

sudo touch /usr/lib/dracut/modules.d/01ramboot/zram-mount.sh sudo chmod 755 /usr/lib/dracut/modules.d/01ramboot/zram-mount.sh sudo nano /usr/lib/dracut/modules.d/01ramboot/zram-mount.sh

add:

#!/bin/sh
. /lib/dracut-lib.sh

if ! getargbool 0 rootzram ; then
    return
fi

mkdir /mnt
umount /sysroot
mount /dev/mapper/qubes_dom0-root /mnt
modprobe zram
echo 10G > /sys/block/zram0/disksize
/mnt/usr/sbin/mkfs.ext2 /dev/zram0
mount /dev/zram0 /sysroot
cp -a /mnt/* /sysroot
exit 0
Press Ctrl + O to save file. Press Ctrl + X to exit nano editor.

+ Step 7. Make New Dracut Config File ramboot.conf:

sudo touch /etc/dracut.conf.d/ramboot.conf sudo nano /etc/dracut.conf.d/ramboot.conf

add:

add_drivers+=" zram "
add_dracutmodules+=" ramboot "
Press Ctrl + O to save file.

Press Ctrl + X to exit nano editor.

+ Step 8. Regenerate with New Dracut Automation Module:

sudo dracut --verbose --force

+Step 9. Creating a script to automatically 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")



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_mem=max:10240M 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_mem=max:10240M 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

Press Ctrl + O to save file. Press Ctrl + X to exit nano editor.

Edit 40_custom file and update GRUB:

sudo /usr/local/bin/grub-custom.sh

+ Step 10. 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 😉

g|689x434

> 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, just run sudo /usr/local/bin/grub-custom.sh and the live modes will work with the new kernels.

> 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 tmpfs setup).

🔦 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: 1|461x130

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)\] '
1|401x186

2|468x227 1|586x177

If you have limited memory, use this guide: https://forum.qubes-os.org/t/really-disposable-ram-based-qubes/21532