I had a Droplet created from a DigitalOcean Custom Image that was only using a small part of its hard disk. This was because partitions are created at the time of installation of the OS (Debian, in this case). In other words, the size and layout of the disk had been frozen when the image was created and, naturally, would not change on its own.

My initial idea was to use GParted to increase the size, but it isn’t amenable to automation. The same goes for fdisk. growpart is supposed to automate the process, but it requires extra packages. sfdisk sounded like the right tool for the job. (I confirmed it can resize active partitions.)

I ran into two problems. First, my image had LVM (Logical Volume Manager) enabled, creating a four-level hierarchy of disk (full size), partition (limited), logical volume group, and logical volume (more limited). The volumes can be inspected with gvs to list the groups and lvdisplay to list the volumes. (The volume identifiers look like paths but aren’t.) They can be grown with lvextend, but I decided to disable LVM during installation instead as I had no use for logical volumes.

Second, the swap partition was blocking the free space on the disk. It’s possible to remove it at runtime (warning: these commands are destructive and can render the disk unreadable; test first and make sure you have backups):

Bashswapoff -a
lvremove $VOLUME_ID # if using LVM
sfdisk --delete $DEVICE_PATH $VOLUME_ID # if using regular partition

I felt it would be better, however, to disable the swap partition entirely with a custom partman recipe. After all, I couldn’t know in advance what the correct size is for the Droplets I’ll deploy the image to. I was previously using the atomic recipe. I replaced it with one adapted from the linked Stack Overflow answer:

Preseed filed-i partman-basicfilesystems/no_swap boolean false
d-i partman-auto/expert_recipe string oneroot :: 1000 6000 10000 ext4 \
    $primary{ } $bootable{ } method{ format } \
    format{ } use_filesystem{ } filesystem{ ext4 } \
    mountpoint{ / } \
    .
d-i partman-auto/choose_recipe select oneroot

Which ultimately makes this work (warning: these commands are destructive and can render the disk unreadable; test first and make sure you have backups):

Bash# The values for the variables can be taken from lsblk (e.g.
# `PARTITION_MINOR_NUMBER=2`, `DEVICE_PATH=/dev/vda`).

# 1. Resize the partition.
echo "- +" | sfdisk --force -N $PARTITION_MINOR_NUMBER $DEVICE_PATH
# 2. Update the kernel’s partition table.
partx -u -n :-1 $DEVICE_PATH
# 3. Resize the underlying filesystem to match the partition.
resize2fs $DEVICE_PATH$PARTITION_MINOR_NUMBER

Now df reflects the correct size without rebooting.

If the lack of swap is a problem later, I’ll have to consider my options. I could stick to a fixed-size swap partition of 1 GB or so if I really had to. Alternatively, I could leave a gap of another GB between the swap and the main partition so I had a bit of room to grow. Or I could create a swap partition at the end of the disk with some convoluted calculations that I hope I get right.