Fork me on GitHub

Shrinking The Root Filesystem

The scenario: You want to change your disk layout, which involves shrinking the partition with the root file system.

You know that you can grow a file system on the fly. At least: most filesystem types

But shrinking a file system invariably means umounting it first.

And that is a problem: You cannot umount the root file system because it is in use. And you need the root file system while the system is running. And you need a (somewhat) running system to shrink the root file system.

It seems like a chicken-and-egg problem.

The Boot Sequence

The boot sequence for a Linux system is a little complicated as it has to (literally) start from scratch. So there are multiple levels of bootstrapping.

For example, in a legacy system with BIOS boot, it looks like this:

  • The BIOS reads the boot sector executes it. That's it. It's all the BIOS knows to do.

This bootstrap program must be small because it has to fit in a single sector. For a long time, the size of a sector has been 512 bytes.

  • The boot sector contains the GNU Grub Bootloader

  • The Grub Bootlader loads other files (typically from /boot/grub) to get Grub itself up and running.

  • Grub presents a simple menu to the user, allowing the user to select an operating system. In may setups, this menu is hidden, and Grub is configured to just load the first item in the menu.

  • Grub loads the linux kernel into memory

  • Grub loads the initrd (Initial RAM Disk) into memory

  • Grub hands over control to the kernel, pointing it to the initrd.

  • As part of the Linux initialisation, various kernel modules are loaded from the initrd - mostly device drivers and whatever is needed to actually mount the root file system later on.

  • The code in the initrd mounts the real root file system, and "pivots" to this.

  • The kernel starts the init process - nowadays: this is systemd.

  • systemd starts whatever system services are installed (e.g. other file systems, network interfaces etc) One of these system services is usually a loging prompt.

  • The user can (finally) log in

The point here is that a lot of stuff happens before the root file system is mounted. The bit we are interested in is the initrd: This is under our control. And this is what allows us to "do stuff" before the root file system is mounted.

Exactly how we control the initrd depends on your Linux distribution - the below is correct for the Debian distribution - and probably works in derivatives such as Ubuntu.

Adding Stuff to the initrd

On Debian we can add bits to the initrd (or initramfs) by dropping files into the directories under /etc/initramfs-tools/ and running update-initramfs.

First: we need to make sure that the tools for doing the resizing is actually available in the initrd by adding a "hook" to the initramfs generation - we do that by adding this file as /etc/initramfs-tools/hooks/resize-root-fs:

#!/bin/sh
set -e
PREREQS=""
prereqs() { echo "$PREREQS"; }
case $1 in
   prereqs)
       prereqs
       exit 0
   ;;
esac
. /usr/share/initramfs-tools/hook-functions
copy_exec /sbin/e2fsck
copy_exec /sbin/resize2fs
exit 0

Then we need the actual script which will run at boot time by adding this as /etc/initramfs-tools/scripts/local-premount/resize-root-fs:

(adjust the values of ROOT_DEV and SIZE to suit your requirements)

#!/bin/sh
#
set -e
PREREQS=""
prereqs() { echo "$PREREQS"; }
case "$1" in
   prereqs)
       prereqs
       exit 0
   ;;
esac

# Device from which the root file system is mounted
ROOT_DEV=/dev/mapper/mylaptop--vg-root

# Desired size of the root file system
SIZE=500G

echo Resizing Root file system...

/sbin/e2fsck -yf ${ROOT_DEV}
/sbin/resize2fs -p ${ROOT_DEV} ${SIZE}
/sbin/e2fsck -yf ${ROOT_DEV}

Finally: We can generate a new initrd with:

update-initramfs -u

and reboot. The root file system will then be shrunk on the next reboot.

TidyUp

With the above in the initrd, the root file system will be shrunk on every reboot. Which is probably not what you want.

Once the root file system has been shrunk, you can undo the above changes with:

rm -f /etc/initramfs-tools/scripts/local-premount/resize-root-fs
rm -f /etc/initramfs-tools/hooks/resize-root-fs
update-initramfs -u

which will generate an initrd which is back to normal.

Final words

If you know you need to shrink the root file system to e.g. 500GB, there is a little trick to avoid trouble:

Resize the filesystem to something smaller than 500GB (but still big enough to contain the files) - e.g. 450GB

This will give you a margin of error when you later on resize the partition on which the root file system resides. Why? Because Gigabytes aren't necessarilty Gigabytes. Or at least: Gigabytes are easily confused with Gibibytes.

Some tools work with 1GB = 10^9 bytes (= 1,000,000,000 bytes), others work on the basis of 1GB = 2^30 bytes (= 1,073,741,824 bytes). That is a 7% difference! And accidentally chopping off 7% at the end of a working file system can spell disaster...