Guide: Offline VM with Encrypted Disk on Qubes OS - V4 Setup: /dev/sdb1 - Internal Drive - Manual Unlock

Good evening everyone, after 5–6 hours of research and discussions with Claude, I finally managed to piece everything together.

Below is the finalized manual edited by Claude.

The goal was to have my sdb1 attached to the VM with the memory configured as a parameter, because I use DispVMs to transfer files via SSH. But depending on the file size, I couldn’t transfer them from one VM to another due to lack of space. So I “found” a solution that works for me while keeping the partition encrypted — therefore inaccessible and invisible at first glance.

Thank you.

What We’re Building

You’re gonna create an offline VM with 900 GB of private storage on your /dev/sdb1 partition (encrypted). The disk will be unlocked manually when you need it, then locked back up after you’re done.

Why this rocks:

  • :lock: Encrypted disk (password protected)
  • :dart: Dedicated VM with 900 GB accessible
  • :electric_plug: Manual activation/deactivation
  • :floppy_disk: Automatic snapshots for backups

:gear: PREREQUISITES

Check your partition in dom0

bash

lsblk -f | grep sdb

You should see sdb1 with ~900G

Check if you have the tools

bash

sudo dnf list installed lvm2 cryptsetup

If missing:

bash

sudo dnf install lvm2 cryptsetup

:closed_lock_with_key: STEP 1: Encrypt the Disk

:warning: WARNING: This step ERASES all data on /dev/sdb1

1.1 Clean up the partition

bash

# Unmount if mounted
sudo umount /dev/sdb1 2>/dev/null

# Wipe existing signatures
sudo wipefs -a /dev/sdb1

1.2 Encrypt with LUKS

bash

sudo cryptsetup luksFormat /dev/sdb1

What’s gonna happen:

  • Type YES in capitals
  • Enter your passphrase (≥12 chars, make it strong!)
  • Confirm the passphrase

:floppy_disk: IMPORTANT: Save this passphrase somewhere safe!

1.3 Verify encryption

bash

lsblk -f | grep sdb1

You should see crypto_LUKS as the filesystem type.


:unlock: STEP 2: Unlock the Disk

bash

sudo cryptsetup luksOpen /dev/sdb1 myencrypted

Breaking it down:

  • sudo cryptsetup luksOpen = FIXED COMMAND
  • /dev/sdb1 = YOUR PARTITION (fixed)
  • myencrypted = NAME you choose (keep this one)

What it asks: Your LUKS passphrase

Verify it worked:

bash

lsblk

You should see /dev/mapper/myencrypted


:package: STEP 3: Create the LVM Structure

3.1 Create the Physical Volume

bash

sudo pvcreate /dev/mapper/myencrypted

Breaking it down:

  • sudo pvcreate = FIXED COMMAND
  • /dev/mapper/myencrypted = Uses the name from step 2

Verify:

bash

sudo pvdisplay

You should see your PV with ~900G


3.2 Create the Volume Group

bash

sudo vgcreate myvg /dev/mapper/myencrypted

Breaking it down:

  • sudo vgcreate = FIXED COMMAND
  • myvg = NAME you choose (keep this one)
  • /dev/mapper/myencrypted = Reuses the name from step 2

Check available size:

bash

sudo vgdisplay myvg

:mag: NOTE the “VG Size” (example: 899.50 GiB)


3.3 Create the Thin Pool

Use 100% of available space:

bash

sudo lvcreate -l 100%FREE -T myvg/mypool

Breaking it down:

  • sudo lvcreate = FIXED COMMAND
  • -l 100%FREE = Uses all free space
  • -T = Creates a thin pool (for snapshots)
  • myvg/mypool = VG/pool name

Verify:

bash

sudo lvs -a myvg

You should see mypool with type twi-a-tz--


:desktop_computer: STEP 4: Connect to Qubes

4.1 Add the pool to Qubes

:warning: CORRECT SYNTAX (spaces between each -o - this is important!):

bash

qvm-pool add mypool lvm_thin -o volume_group=myvg -o thin_pool=mypool -o revisions_to_keep=3

Breaking it down:

  • qvm-pool add mypool = Pool name in Qubes
  • lvm_thin = Pool type
  • -o volume_group=myvg = Your VG (step 3.2)
  • -o thin_pool=mypool = Your pool (step 3.3)
  • -o revisions_to_keep=3 = Keeps 3 snapshots

Verify:

bash

qvm-pool list

You should see mypool with driver lvm_thin


:rocket: STEP 5: Create the Offline VM

5.1 List your available templates

bash

qvm-ls --template

Note a template (examples: debian-12, fedora-40, debian-11)


5.2 Create the VM

:warning: Replace debian-12 with YOUR template!

bash

qvm-create --pool private=mypool --label red --template debian-12 --standalone --property netvm=none my-offline-vm

Breaking it down:

  • qvm-create = FIXED COMMAND
  • --pool private=mypool = Uses your pool
  • --label red = Color (changeable: blue, green, orange…)
  • --template debian-12 = YOUR TEMPLATE (change it!)
  • --standalone = Independent VM
  • --property netvm=none = No network (offline)
  • my-offline-vm = VM name (changeable)

5.3 Extend the private volume

bash

qvm-volume extend my-offline-vm:private 850G

Why 850G? Leaves 50G margin for snapshots.

Verify:

bash

qvm-volume info my-offline-vm:private

You should see size: 850 GiB


:white_check_mark: STEP 6: Test the VM

6.1 Start the VM

bash

qvm-start my-offline-vm

Wait 20-30 seconds (first-time initialization).

6.2 Open a terminal in the VM

Method 1 - Via Qube Manager:

  • Open Qube Manager (menu)
  • Find my-offline-vm (red)
  • Right-click → Run Terminal

Method 2 - From dom0:

bash

qvm-run my-offline-vm xterm

6.3 Check available space (INSIDE the VM)

bash

df -h /home

:white_check_mark: You should see ~850G available!

6.4 Test writing

bash

echo "Secure storage test" > ~/test.txt
cat ~/test.txt

:tada: If it works: CONGRATS, everything is operational!


:memo: DAILY USE: How to Use It

:white_check_mark: START AND USE THE VM

1. Unlock the disk (in dom0):

bash

sudo cryptsetup luksOpen /dev/sdb1 myencrypted
sudo vgchange -ay myvg

2. Start the VM:

bash

qvm-start my-offline-vm

3. Use normally via Qube Manager or terminal


:stop_sign: STOP AND LOCK THE DISK

1. Stop the VM:

bash

qvm-shutdown my-offline-vm

Wait until it’s completely stopped (qvm-ls no longer shows it in green)

2. Deactivate the VG:

bash

sudo vgchange -an myvg

3. Lock the disk:

bash

sudo cryptsetup luksClose myencrypted

:white_check_mark: Secured: The disk is now completely inaccessible


:arrows_counterclockwise: Automation Scripts (Optional)

Startup script: ~/start-offline-vm.sh

bash

#!/bin/bash
echo "🔓 Unlocking disk..."
sudo cryptsetup luksOpen /dev/sdb1 myencrypted || exit 1
sudo vgchange -ay myvg || exit 1
echo "🚀 Starting VM..."
qvm-start my-offline-vm
echo "✅ VM ready!"

Shutdown script: ~/stop-offline-vm.sh

bash

#!/bin/bash
echo "🛑 Stopping VM..."
qvm-shutdown --wait my-offline-vm
echo "🔒 Locking disk..."
sudo vgchange -an myvg
sudo cryptsetup luksClose myencrypted
echo "✅ Disk secured!"

Make them executable:

bash

chmod +x ~/start-offline-vm.sh ~/stop-offline-vm.sh

Use them:

bash

~/start-offline-vm.sh
# ... your work ...
~/stop-offline-vm.sh

:outbox_tray: File Transfer

From a networked VM to my-offline-vm:

bash

# In the source VM
qvm-copy /path/to/file

Select my-offline-vm in the window

In my-offline-vm, files arrive in:

~/QubesIncoming/source-vm-name/

:wrench: Verification Commands

bash

# Check if disk is unlocked
lsblk | grep myencrypted

# Check if VG is active
sudo vgs

# Check pool space usage
qvm-pool info mypool

# Check VM space
qvm-volume info my-offline-vm:private

# VM status
qvm-ls | grep my-offline-vm

:warning: Troubleshooting

:x: “Got empty response from qubesd”

Solution:

bash

# Check VG is active
sudo vgs

# If "inactive":
sudo vgchange -ay myvg

# Restart qubesd
sudo systemctl restart qubesd

# Retry with SPACES between each -o:
qvm-pool add mypool lvm_thin -o volume_group=myvg -o thin_pool=mypool -o revisions_to_keep=3

:x: “Device or resource busy” during luksClose

bash

sudo vgchange -an myvg --force
sudo cryptsetup luksClose myencrypted

:x: VM won’t start (“volume missing”)

Cause: Disk not unlocked

bash

sudo cryptsetup luksOpen /dev/sdb1 myencrypted
sudo vgchange -ay myvg
qvm-start my-offline-vm

:x: Forgot LUKS passphrase

Unfortunately: LUKS without passphrase = data lost forever
Prevention: Save the passphrase in a secure password manager


:wastebasket: Complete Removal

bash

# 1. Stop the VM
qvm-shutdown my-offline-vm

# 2. Remove the VM
qvm-remove my-offline-vm

# 3. Remove the pool
qvm-pool remove mypool

# 4. Destroy LVM structure
sudo lvremove myvg/mypool
sudo vgremove myvg
sudo pvremove /dev/mapper/myencrypted

# 5. Close LUKS
sudo cryptsetup luksClose myencrypted

# 6. (Optional) Completely erase sdb1
sudo cryptsetup luksErase /dev/sdb1  # WARNING: Permanent loss!
sudo wipefs -a /dev/sdb1

:closed_lock_with_key: Change LUKS Passphrase

bash

sudo cryptsetup luksChangeKey /dev/sdb1

Enter old passphrase, then new one (twice).

I’m not entirely sure I understand the use case of this? It adds a local storage to the pool with its own luks password, and you can add/remove it for a single qube. I don’t really see the point, but you certainly understand your use case better than me.

1 Like

Thank you so much for organizing and publishing your hard work. Now I have a general idea of ​​how to add a vault to the vault qubes when the time comes. Good work and best regards.

Hello,

Thank you for your answer.

Yes, that’s correct. I’m adding an additional security layer to my offline VM.
By using a separate encrypted storage device with its own LUKS passphrase, even if the offline VM were somehow compromised, it would not be able to access or enumerate anything on that storage. In other words, the VM remains functionally isolated from the underlying data. :see_no_evil: :hear_no_evil: :speak_no_evil:

1 Like

The documentation is not clear about the pool parameter:

-P POOL
Pool to use for the new domain. All volumes besides snapshots volumes are imported in to the specified POOL. THIS IS WHAT YOU WANT TO USE NORMALLY.

--pool=POOL:VOLUME, -p POOL:VOLUME
Specify the pool to use for the specific volume

I recommend to double check the snapshots are on the right pool. This is highly probable but the documentation gives me a doubt.

Thank you for your answer.

I live in a country where life is already difficult, and it will be even more so in the future…
I haven’t bought the computer yet, but I plan to get a NitroPC Pro 2. So I’m experimenting with Qubes now, in order to be able to use it properly when the time comes.

Thanks for your reply. You mean to ask, ‘Where is /root going?’ right?


I looked at what you asked me, and I have (at least, if I understood correctly) 2 snapshots.

Using qvm-volume info nameVM, I get:

list of available revisions:
xxxx-back
xxxx-back

@User.LinuxOne

I’m adding an additional security layer to my offline VM.
By using a separate encrypted storage device with its own LUKS passphrase, even if the offline VM were somehow compromised, it would not be able to access or enumerate anything on that storage. In other words, the VM remains functionally isolated from the underlying data.

How is a VM functionally isolated from the storage it:

  1. Unlocks (knows the secret to)
  2. Uses for read/write operation

Please also note that after this

sudo cryptsetup luksOpen /dev/sdb1 myencrypted

the unencrypted drive can be attached to any VM. All that is required is a user-side mistake to attach the device to the wrong VM.

I do consider this a security problem. We discussed it in this thread:

Preparing the strategy is half the battle, I wish you the best of luck, regards.