Use systemd service to automatically extend existing ext4 filesystem on boot or on-demand to optimize virtual machine configuration.

Debian version.

$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 10 (buster)
Release:        10
Codename:       buster

Install cloud-guest-utils to take advantage of the growpart shell script.

$ sudo apt-get install growpart

Display current disk partitions.

$ sudo fdisk --list /dev/sda
Disk /dev/sda: 9 GiB, 9663676416 bytes, 18874368 sectors
Disk model: VBOX HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xf316c96f
Device     Boot Start      End  Sectors  Size Id Type
/dev/sda1  *     2048 18669567 18667520  8.9G 83 Linux

List block devices.

$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0  9.9G  0 disk
`-sda1   8:1    0  8.9G  0 part /

Create a systemd service that will extend root partition (disk /dev/sda, partition 1) in a partition table to fill available space and resize the ext4 file system.

$ cat <<EOF | sudo tee /etc/systemd/system/growpart-and-resizefs-root.service
[Unit]
Description=Extend root partition and resize ext4 file system
After=local-fs.target
Wants=local-fs.target

[Service]
Environment=ROOT_DISK=/dev/sda
Environment=ROOT_PARTITION=1
ExecStart=/bin/bash -c "/usr/bin/growpart -N \${ROOT_DISK} \${ROOT_PARTITION} && /usr/bin/growpart \${ROOT_DISK} \${ROOT_PARTITION} || exit 0"
ExecStop=/bin/bash -c "/sbin/resize2fs \${ROOT_DISK}\${ROOT_PARTITION} || exit 0"
Type=oneshot

[Install]
WantedBy=multi-user.target
EOF

Reload systemd manager configuration.

$ sudo systemctl daemon-reload

Enable service at boot.

$ sudo systemctl enable growpart-and-resize-root
Created symlink /etc/systemd/system/multi-user.target.wants/growpart-and-resizefs-root.service -> /etc/systemd/system/growpart-and-resizefs-root.service.
It will automatically extend the existing ext4 filesystem on boot to use all available space.

Stop the virtual machine, extend the hard disk, and boot it again.

$ sudo fdisk --list /dev/sda
Disk /dev/sda: 9.9 GiB, 10632560640 bytes, 20766720 sectors
Disk model: VBOX HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xf316c96f
Device     Boot Start      End  Sectors  Size Id Type
/dev/sda1  *     2048 20766686 20764639  9.9G 83 Linux
$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0  9.9G  0 disk
`-sda1   8:1    0  9.9G  0 part /

Service status after the successful operation.

$ sudo systemctl status  growpart-and-resizefs-root
* growpart-and-resizefs-root.service - Extend root partition and resize ext4 file system
   Loaded: loaded (/etc/systemd/system/growpart-and-resizefs-root.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since Mon 2020-02-17 22:14:04 GMT; 46s ago
  Process: 1830 ExecStart=/bin/bash -c /usr/bin/growpart -N ${ROOT_DISK} ${ROOT_PARTITION} && /usr/bin/growpart ${ROOT_DISK} ${ROOT_PARTITION} || exit 0 (code=exited, status=0/SUCCESS)
  Process: 1873 ExecStop=/bin/bash -c /sbin/resize2fs ${ROOT_DISK}${ROOT_PARTITION} || exit 0 (code=exited, status=0/SUCCESS)
 Main PID: 1830 (code=exited, status=0/SUCCESS)
Feb 17 22:14:03 debian bash[1830]: device: /dev/sda
Feb 17 22:14:03 debian bash[1830]: unit: sectors
Feb 17 22:14:03 debian bash[1830]: /dev/sda1 : start=        2048, size=    20764639, type=83, bootable
Feb 17 22:14:04 debian bash[1830]: CHANGED: partition=1 start=2048 old: size=18667520 end=18669568 new: size=20764639,end=20766687
Feb 17 22:14:04 debian bash[1873]: resize2fs 1.44.5 (15-Dec-2018)
Feb 17 22:14:04 debian bash[1873]: Filesystem at /dev/sda1 is mounted on /; on-line resizing required
Feb 17 22:14:04 debian bash[1873]: old_desc_blocks = 2, new_desc_blocks = 2
Feb 17 22:14:04 debian bash[1873]: The filesystem on /dev/sda1 is now 2595579 (4k) blocks long.
Feb 17 22:14:04 debian systemd[1]: growpart-and-resizefs-root.service: Succeeded.
Feb 17 22:14:04 debian systemd[1]: Started Extend root partition and resize ext4 file system.

Service logs after the successful operation.

$ sudo journalctl -u growpart-and-resizefs-root
-- Logs begin at Sun 2020-02-16 21:04:26 GMT, end at Mon 2020-02-17 22:15:28 GMT. --
Feb 17 22:14:03 debian systemd[1]: Starting Extend root partition and resize ext4 file system...
Feb 17 22:14:03 debian bash[1830]: CHANGE: partition=1 start=2048 old: size=18667520 end=18669568 new: size=20764639,end=20766687
Feb 17 22:14:03 debian bash[1830]: # === old sfdisk -d ===
Feb 17 22:14:03 debian bash[1830]: label: dos
Feb 17 22:14:03 debian bash[1830]: label-id: 0xf316c96f
Feb 17 22:14:03 debian bash[1830]: device: /dev/sda
Feb 17 22:14:03 debian bash[1830]: unit: sectors
Feb 17 22:14:03 debian bash[1830]: /dev/sda1 : start=        2048, size=    18667520, type=83, bootable
Feb 17 22:14:03 debian bash[1830]: # === new sfdisk -d ===
Feb 17 22:14:03 debian bash[1830]: label: dos
Feb 17 22:14:03 debian bash[1830]: label-id: 0xf316c96f
Feb 17 22:14:03 debian bash[1830]: device: /dev/sda
Feb 17 22:14:03 debian bash[1830]: unit: sectors
Feb 17 22:14:03 debian bash[1830]: /dev/sda1 : start=        2048, size=    20764639, type=83, bootable
Feb 17 22:14:04 debian bash[1830]: CHANGED: partition=1 start=2048 old: size=18667520 end=18669568 new: size=20764639,end=20766687
Feb 17 22:14:04 debian bash[1873]: resize2fs 1.44.5 (15-Dec-2018)
Feb 17 22:14:04 debian bash[1873]: Filesystem at /dev/sda1 is mounted on /; on-line resizing required
Feb 17 22:14:04 debian bash[1873]: old_desc_blocks = 2, new_desc_blocks = 2
Feb 17 22:14:04 debian bash[1873]: The filesystem on /dev/sda1 is now 2595579 (4k) blocks long.
Feb 17 22:14:04 debian systemd[1]: growpart-and-resizefs-root.service: Succeeded.
Feb 17 22:14:04 debian systemd[1]: Started Extend root partition and resize ext4 file system.

Service status and logs when hard disk size has not changed.

$ sudo systemctl status growpart-and-resizefs-root
* growpart-and-resizefs-root.service - Extend root partition and resize ext4 file system
   Loaded: loaded (/etc/systemd/system/growpart-and-resizefs-root.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since Mon 2020-02-17 22:16:14 GMT; 1s ago
  Process: 1897 ExecStart=/bin/bash -c /usr/bin/growpart -N ${ROOT_DISK} ${ROOT_PARTITION} && /usr/bin/growpart ${ROOT_DISK} ${ROOT_PARTITION} || exit 0 (code=exited, status=0/SUCCESS)
  Process: 1915 ExecStop=/bin/bash -c /sbin/resize2fs ${ROOT_DISK}${ROOT_PARTITION} || exit 0 (code=exited, status=0/SUCCESS)
 Main PID: 1897 (code=exited, status=0/SUCCESS)
Feb 17 22:16:14 debian systemd[1]: Starting Extend root partition and resize ext4 file system...
Feb 17 22:16:14 debian bash[1897]: NOCHANGE: partition 1 is size 20764639. it cannot be grown
Feb 17 22:16:14 debian bash[1915]: resize2fs 1.44.5 (15-Dec-2018)
Feb 17 22:16:14 debian bash[1915]: The filesystem is already 2595579 (4k) blocks long.  Nothing to do!
Feb 17 22:16:14 debian systemd[1]: growpart-and-resizefs-root.service: Succeeded.
Feb 17 22:16:14 debian systemd[1]: Started Extend root partition and resize ext4 file system.

Additional notes

VirtualBox does not allow online disk expansion, but VMware does. In the latter case, you can rescan the root device and start systemd service to extend the root partition and resize the ext4 file system.

$ echo 1 | sudo tee /sys/block/sda/device/rescan
$ sudo systemctl start growpart-and-resizefs-root