How to compact VirtualBox's VDI file size?

Solution 1:

You have to do the following steps:

  1. Run defrag in the guest (Windows only)

  2. Nullify free space:

    With a Linux Guest run this:

     dd if=/dev/zero of=/var/tmp/bigemptyfile bs=4096k ; rm /var/tmp/bigemptyfile
    

    Or:

     telinit 1
     mount -o remount,ro /dev/sda1
     zerofree -v /dev/sda1
    

    With a Windows Guest, download SDelete from Sysinternals and run this:

     sdelete.exe c: -z
    

(replace C: with the drive letter of the VDI)

  1. Shutdown the guest VM

  2. Now run VBoxManage's modifymedium command with the --compact option:

    With a Linux Host run this:

     vboxmanage modifymedium --compact /path/to/thedisk.vdi
    

    With a Windows Host run this:

     VBoxManage.exe modifymedium --compact c:\path\to\thedisk.vdi
    

    With a Mac Host run this:

     VBoxManage modifymedium --compact /path/to/thedisk.vdi
    

    VBoxManage is located here: /Applications/VirtualBox.app/Contents/MacOS/VBoxManage

This reduces the vdi size.

Solution 2:

I'm on a Windows 7 host with Windows guests, Here is a batch file I wrote to Compact all of the VDIs in a folder tree

echo off
mode con:cols=140 lines=200
cls
:: see https://forums.virtualbox.org/viewtopic.php?p=29272#p29272
:: How can I reduce the size of a dynamic VDI on disk?
:: but that page says to use sdelete -s which is suboptimal. 
:: use -z as per http://technet.microsoft.com/en-us/sysinternals/bb897443.aspx

:: First run the sdelete -z c: inside the VMs that zero-out all the free space
:: THEN run this batch file 

Title Compacting Free space on Virtual Machine VMs

:: http://ss64.com/nt/for_r.html
:: http://stackoverflow.com/questions/8836368/windows-batch-file-how-to-loop-through-files-in-a-directory/8836401#8836401

Setlocal EnableDelayedExpansion
:: http://ss64.com/nt/delayedexpansion.html ... 
:: Notice that within the for loop we use !variable! instead of %variable%.

For /R %CD% %%G IN (*.vdi) DO (
 set ohai=%%G
 set lastfive=!ohai:~-5!
:: Skip snapshots which are named {guid}.vdi
 if NOT !lastfive!==}.vdi (
 echo .
 echo Compacting %%G
 "C:\Program Files\Oracle\VirtualBox\VboxManage.exe" modifyhd "%%G" --compact )
 )
 
pause 
exit

I left the links in the comments so you can (sort of) tell how it works.

edit

Well, after all that, I tried the CloneVDI tool and it did a good job in much less time and in one click.

Solution 3:

Debian guest on Windows host using discard/TRIM.

This isn't a direct answer per se, as I'm addressing the problem, not the question. Instead of periodically compacting the image, this solution uses discard to automatically remove unused blocks in the host's VM disk image.

This solution requires a guest filesystem that supports continuous TRIM. The Arch Linux wiki has a list of filesystems supporting TRIM operations.

FDE and cryptoroot are specifically not covered, as there are security concerns and none of the other solutions to this question would allow compacting either. The Arch Linux wiki has information about TRIM and dm-crypt devices.

In theory, this will work for all Linux guests on VBox hosts using VDI storage.

Host configuration

With VBox exited and no VMs running, add discard support to your disks by setting both discard and nonrotational for each disk in the config file for the VM. At this time discard is not in the GUI, but nonrotational is exposed as the "Solid-state Drive" checkbox. (ref: vbox forums, discard support)

<AttachedDevice discard="true" nonrotational="true" type="HardDisk" [..other options..] >

Boot the VM up, and verify that TRIM support is enabled:

sudo hdparm -I /dev/sda | grep TRIM

Guest Configuration

If LVM is in use, change the discard setting in /etc/lvm/lvm.conf. (ref: debian wiki, lvm.conf example)

devices {
...
    issue_discards = 1
}

In fstab, add the discard option to the filesystems you wish to auto-discard (ref: debian wiki, fstab example)

UUID=8db6787f-1e82-42d8-b39f-8b7491a0523c   /   ext4    discard,errors=remount-ro   0   1
UUID=70bfca92-8454-4777-9d87-a7face32b7e7   /build  ext4    discard,errors=remount-ro,noatime   0   1

Remount the filesystems to have them pick up their new options.

sudo mount -o remount /
sudo mount -o remount /build

Manually trim free blocks now with fstrim. fstrim uses the mounted filesystem, not the block device backing it. Instead of setting continuous discard in fstab, this could be done on a weekly cron. (The weekly cron is recommended for physical SSDs which may have questionable support for TRIM, but this is not relevant here since underlying SSDs are handled by the host OS. see: ssd trim warning).

fstrim /
fstrim /build

At this point, the size of the filesystems inside the VM and the size of the VM images should be pretty close in value.

Tested with:

  • Guest1: Debian 8.7, kernel: linux 4.8 grsec from backports, filesystem: ext4
  • Guest2: Debian 9 RC2, kernel: linux 4.9, filesystem: ext4
  • Host1: VBox 5.1.14, Win7, image fmt: VDI
  • Host2: VBox 5.1.14, Win8.1, image fmt: VDI

Solution 4:

For MacOS Guest do this:

  1. Nullify free space in guest system:

    diskutil secureErase freespace 0 "/Volumes/Macintosh HD"
    

    (replace /Volumes/Macintosh HD with your drive name)

  2. Shutdown the guest VM

  3. Run this command to reduce VDI disk image size

    VBoxManage modifyhd /path/to/thedisk.vdi --compact
    

    OR

    VBoxManage modifymedium /path/to/thedisk.vdi --compact