Grow a Linux disk after extending a VMDK

A hands-on Ubuntu Server lab: extend the VMDK from 60 to 80 GB in vSphere, then grow the partition, the LVM physical volume, the logical volume and the ext4 filesystem, checking every layer as you go.

Extending a virtual disk in vSphere takes about fifteen seconds. The part that surprises people is what happens next: the guest reports exactly the same free space it had before, and it keeps reporting it until four more layers are told about the change.

Between the VMDK and the directory that ran out of space sit a disk device, a partition, an LVM physical volume, a logical volume and a filesystem. Each has its own idea of how big it is, and each is grown by a different command. Miss one and the extra space stays invisible.

This page is written as a lab. It builds one small Ubuntu Server VM, fills its root filesystem on purpose, extends the VMDK from 60 GB to 80 GB and then walks every layer up to df, with a check after each command. Every screenshot comes from that VM, so you can rebuild it and compare your screen with this one step by step.

Applies to: vSphere 7 and 8, with an Ubuntu Server guest installed with the default guided LVM layout and an ext4 root filesystem.


The chain, end to end

Five layers, five commands. This table is the whole lab in short form: if you already know the sequence and only need the commands, work down it.

StepLayerCommand
1The VMDK in vSphereEdit Settings, Hard disk 1 from 60 to 80 GB
2The partitiongrowpart /dev/sda 3
3The LVM physical volumepvresize /dev/sda3
4The logical volumelvextend -L +20G /dev/ubuntu-vg/ubuntu-lv
5The ext4 filesystemresize2fs /dev/ubuntu-vg/ubuntu-lv

Read the chain from the bottom up. vSphere makes the virtual disk bigger. The partition sda3 sits on that disk and has to be stretched into the new space. LVM keeps its own record of how big the partition is, so the physical volume is told next. Only then does the volume group have free space to hand to the logical volume, and only when the logical volume is bigger can the filesystem inside it grow. df reports the last layer, which is why it is the last number to change.

Note: Every guest step runs online. None of them needs a reboot, and the root filesystem stays mounted throughout. In this lab the VM was powered off for the vSphere step only because that is the simplest way to be sure the guest reads the new disk size.

What LVM is: PV, VG and LV

LVM, the Logical Volume Manager, adds a layer between the partition and the filesystem. Instead of formatting sda3 directly, the installer handed it to LVM. LVM pools that space and cuts volumes out of the pool, and the filesystem lives in one of those volumes. That extra layer is why growing / takes three LVM-aware steps rather than one, and also why it can be done online at all.

Three terms cover everything on this page:

TermWhat it isIn this labShow it
PV, physical volumeA disk or partition handed over to LVM/dev/sda3sudo pvs
VG, volume groupA pool built from one or more PVs, cut into equal chunks called extentsubuntu-vgsudo vgs
LV, logical volumeA slice of the pool that behaves like a partition; the filesystem lives hereubuntu-lvsudo lvs
Diagram of the LVM layers on the Ubuntu lab VM: disk sda 80G with partition sda3 78G, physical volume /dev/sda3, volume group ubuntu-vg with 49G given to ubuntu-lv and 29G free, logical volume ubuntu-lv 49G, and the ext4 filesystem on / with 26G used, next to the commands lsblk, pvs, vgs, lvs and df and the grow commands growpart, pvresize, lvextend and resize2fs
The five layers of the lab VM after the extend, with the command that shows each one and the command that grows it.

Read the picture from the bottom up. The partition sda3 becomes a PV. The PV is the only member of the pool ubuntu-vg. The pool gives 49G to ubuntu-lv and keeps 29G unassigned, and ubuntu-lv carries the ext4 filesystem that df reports. /boot on sda2 is outside LVM altogether, which is why it never appears in pvs, vgs or lvs.

If you come from Windows, the nearest picture is Storage Spaces: the VG plays the role of the storage pool, and an LV is a virtual disk carved out of it. As with a pool, a VG can take more than one PV, so a second VMDK can later join the same group and its space can go to the same LV.

The pool is counted in extents, 4 MiB each by default, and that is the unit LVM really works in. The lvextend output in step 5 shows it: ubuntu-lv goes from 7423 to 12543 extents, a difference of 5120 extents of 4 MiB, which is exactly the 20 GiB that was asked for.

Reading pvs, vgs and lvs

Each layer has its own short report command. All three are read-only and safe to run at any time.

# One line per physical volume: which VG it belongs to, its size and free space
sudo pvs

# One line per volume group: how big the pool is and how much of it is unassigned
sudo vgs

# One line per logical volume: which VG it lives in and how big it is
sudo lvs

This is the vgs line from the lab after pvresize, as it appears on the step 7 screenshot:

  VG        #PV #LV #SN Attr   VSize   VFree
  ubuntu-vg   1   1   0 wz--n- <78.00g 49.00g
ColumnMeaningLab value
VGName of the volume groupubuntu-vg
#PVHow many physical volumes it is built from1, only /dev/sda3
#LVHow many logical volumes are cut from it1, only ubuntu-lv
#SNHow many LVM snapshots exist0
AttrFlags: w writeable, z resizeable, n normal allocationwz--n-
VSizeTotal size of the pooljust under 78G
VFreeSpace not yet given to any LV49G

lvs prints LV, VG, Attr and LSize for a plain volume like this one; the remaining columns are for thin pools, snapshots and mirrors and stay empty. For a mounted root volume the Attr field typically reads -wi-ao----: w writeable, a active, o open, meaning something is using it, here the mounted filesystem. pvs follows the same pattern with PV, VG, Fmt, Attr, PSize and PFree.

Note: lvs has no free-space column, because a logical volume is never partly free from LVM's point of view: free space lives in the volume group. That is why the question "is there room to grow the LV?" is answered by VFree in vgs, and the question "is the filesystem full?" by df. For a verbose, one-field-per-line view of the same data, use sudo pvdisplay, sudo vgdisplay and sudo lvdisplay.

Build the lab

One VM is used throughout. These are the values on the screenshots; use your own names and addresses if you like, but keep them consistent so the commands below still match.

SettingLab value
Guest OSUbuntu Server, default guided storage layout with LVM
VM name and hostnameSRV-LNX-01
Userzaur, with sudo rights
Virtual diskHard disk 1, 60 GB, seen by the guest as /dev/sda
Network interfaceenp3s0
Address10.0.0.21/24 on an isolated lab network, no gateway, no DNS

During installation, leave storage on the default Use an entire disk with Set up this disk as an LVM group ticked, and tick Install OpenSSH server. The lab network has no gateway, so nothing can be downloaded later with apt: whatever the installer puts on the disk is what you work with.

Give the VM a static address with Netplan

Ubuntu Server configures the network through Netplan, which reads YAML files from /etc/netplan/. Work at the VM console in vSphere for this part, because SSH is not reachable until the address exists. Start by looking at what the installer left behind.

# List the existing Netplan files before adding one
ls -l /etc/netplan/

Now create the lab file. The tee form writes it in one paste, with no editor needed.

# Write /etc/netplan/01-lab-static.yaml: a fixed IPv4 address, no DHCP
sudo tee /etc/netplan/01-lab-static.yaml > /dev/null <<'EOF'
network:
  version: 2
  ethernets:
    enp3s0:
      dhcp4: false
      dhcp6: false
      addresses:
        - 10.0.0.21/24
EOF

Line by line: enp3s0 is the interface name from ip link, dhcp4: false and dhcp6: false stop the VM waiting for a DHCP server that does not exist on this network, and addresses gives it one fixed address with a /24 mask. There is no routes and no nameservers section because the lab never leaves its own subnet. YAML is strict about indentation: two spaces per level, never tabs.

# Netplan warns when the file is readable by other users; only root needs it
sudo chmod 600 /etc/netplan/01-lab-static.yaml

# Check the syntax and render the configuration without applying it
sudo netplan generate

# Apply it with an automatic rollback if you do not confirm
sudo netplan try

netplan generate is the safe dry step: a typo in the YAML is reported here and nothing changes. netplan try then applies the configuration and waits, 120 seconds by default, for you to press Enter. If you do not confirm, for example because the change cut you off, it rolls back by itself. Once confirmed, check the result:

# The inet line should read 10.0.0.21/24
ip -4 address show enp3s0
Warning: Netplan reads every file in /etc/netplan/ in name order and a later file overrides an earlier one. If the installer left a file such as 50-cloud-init.yaml that also configures enp3s0 with DHCP, it sorts after 01-lab-static.yaml and can win. If ip -4 address show enp3s0 does not show 10.0.0.21/24, open that file and remove the enp3s0 section from it.

Turn on SSH

# Start the SSH server now and at every boot
sudo systemctl enable --now ssh

# Should report active (running)
systemctl status ssh --no-pager

enable makes SSH start at boot, and --now also starts it immediately, so one command covers both. From here the rest of the lab can run over SSH from a Windows machine with an address in the same 10.0.0.0/24 subnet:

ssh zaur@10.0.0.21

The first connection asks you to accept the host key. Connect to a Linux server from Windows covers that prompt, keys and scp in detail.

Note: No firewall is configured in this lab on purpose. The VM sits on an isolated network, and leaving the firewall out keeps the focus on storage. On a real server, the SSH port is the one thing to allow before a firewall is turned on.

Fill the disk and record the starting state

A freshly installed VM has plenty of free space, which makes the before and after pictures look identical. For the lab, fill the root filesystem with one large throwaway file so the problem is visible. Do this only on a lab VM.

# How much space is free right now? Read the Avail column
df -h /

# Allocate one large file instantly. Size it from Avail so / ends up around 90 percent
sudo fallocate -l 20G /var/tmp/lab-fill.img

fallocate reserves the blocks without writing data, so a 20 GB file appears in a second. The file lives in /var/tmp, which is on the root filesystem, and it is removed at the end of the lab. A fresh install on a 29G root uses only a few GB, so 20G of filler lands in the low 90s; on a different layout, pick a size a little below the Avail figure.

Now record the starting state. When a later step seems to do nothing, this is what you compare against.

# Every block device with its size, type, filesystem and mount point
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS

# Type and usage of the root filesystem; -T adds the filesystem type
df -hT /
Ubuntu Server terminal on SRV-LNX-01 showing lsblk with sda at 60G, sda3 at 58G as LVM2_member, ubuntu-vg-ubuntu-lv at 29G mounted on /, and df -hT reporting the ext4 root filesystem 94 percent full
Starting state: sda is 60G, sda3 is 58G, the root logical volume is 29G and / is 94% full.

The lab output, with the snap loop devices and the CD drive sr0 left out:

NAME                        SIZE TYPE FSTYPE      MOUNTPOINTS
sda                          60G disk
├─sda1                        1M part
├─sda2                        2G part ext4        /boot
└─sda3                       58G part LVM2_member
  └─ubuntu--vg-ubuntu--lv    29G lvm  ext4        /

Filesystem                        Type  Size  Used Avail Use% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv ext4   29G   26G  1.7G  94% /

How to read it: sda is the whole virtual disk. sda1 is a 1M BIOS boot partition, sda2 is /boot as plain ext4, and sda3 is the LVM physical volume that carries the rest. Inside it sits one logical volume, ubuntu-lv, formatted ext4 and mounted as /. That last line is the one that is full.

# The logical volumes and the volume groups, with their free space
sudo lvs
sudo vgs

lvs shows ubuntu-lv at about 29G. vgs is the more interesting one: ubuntu-vg is about 58G in size, and its VFree column shows roughly 29G that no logical volume uses yet.

Note: That free space is not an accident. The Ubuntu Server installer gives the root logical volume only part of the volume group and leaves the rest unallocated, so there is room to grow later. It means that on a real server, sudo vgs is the first command to run when / fills up: if VFree is not zero, lvextend and resize2fs alone may be enough and vSphere is not needed at all. This lab extends the VMDK anyway, to show the whole chain.
Warning: The sizes and percentages on this page are this lab's, not universal values. The 29G root, the 94% and later the 49G and 56% depend on the size of the disk, how the installer split the volume group, how much filler you wrote and how much you choose to give the logical volume. Compare the changes between steps, not the absolute numbers.

Step 1. Extend the VMDK

In the vSphere client, shut down SRV-LNX-01, right-click it and choose Edit Settings. On Virtual Hardware, change Hard disk 1 from 60 to 80 GB, click OK and power the VM on again.

vSphere Edit Settings dialog for VM SRV-LNX-01 with Hard disk 1 changed from 60 to 80 GB, marked with an asterisk as a pending change
Hard disk 1 raised from 60 to 80 GB in Edit Settings. The asterisk marks a change that is not saved yet.
Common mistake: If the size field is greyed out, the VM has a snapshot. A disk cannot be extended while snapshots exist. Delete or consolidate them first, and confirm the datastore has room for the growth before you start.

This step changes the virtual hardware and nothing else. The partition table, LVM and ext4 inside the guest still describe a 60 GB disk, and they will keep doing so until each one is grown in turn.


Step 2. Check what the guest sees

Log in again over SSH and look at every layer at once.

# Confirm you are on the right machine and address
ip -4 address show enp3s0

# The disk should be bigger now; the partition should not
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS

# The filesystem has not changed at all
df -hT /
Ubuntu Server terminal after power-on showing ip address 10.0.0.21/24 on enp3s0, lsblk with sda at 80G while sda3 is still 58G and the root logical volume still 29G, and df -hT still at 94 percent
After power-on: sda is 80G, but sda3 is still 58G, the root volume still 29G and df still 94%.

This is the gap the rest of the lab closes. The kernel read the new disk size at boot, so sda shows 80G. Every layer above it still shows its old size, because none of them grows by itself: 20G of unpartitioned space now sits at the end of the disk, after sda3.

Note: If you extend the disk while the VM is running, the kernel does not notice until it is told to look again. Rescan the one device with echo 1 | sudo tee /sys/class/block/sda/device/rescan, then run lsblk again. Powering the VM off and on, as this lab does, makes that step unnecessary.

Step 3. Grow the partition

growpart rewrites the partition table so the chosen partition extends to the end of the free space behind it. It works on a mounted partition, and it ships in the cloud-guest-utils package, which was already installed on the lab VM. Check it is there, then run it.

# Prints the path if growpart is installed, nothing if it is not
command -v growpart

# Grow partition number 3 on /dev/sda into the free space after it.
# The disk and the partition NUMBER are two separate arguments, not /dev/sda3.
sudo growpart /dev/sda 3
CHANGED: partition=3 start=4198400 old: size=121628672 end=125827071 new: size=163573727 end=167772126

The numbers are 512-byte sectors. The start stays the same, which is what keeps the data safe, and only the end moves outward. Check the result:

lsblk
Ubuntu Server terminal showing command -v growpart, sudo growpart /dev/sda 3 reporting CHANGED partition=3, and lsblk with sda3 grown from 58G to 78G
growpart /dev/sda 3 moves the end of the partition: sda3 goes from 58G to 78G.
Result: sda3 is now 78G. The root logical volume still shows 29G, because LVM has not been told yet.
Warning: NOCHANGE: partition 3 is size ... it cannot be grown means there is no free space directly after that partition. Either the disk was not extended, the guest has not seen the new size (go back to step 2), or sda3 is not the last partition on the disk. Use sudo growpart -N /dev/sda 3 for a dry run that reports what it would do without writing anything.

Step 4. Grow the LVM physical volume

LVM stores the size of each physical volume in its own metadata, which still says 58G. pvresize reads the partition again and updates that record.

# Tell LVM that /dev/sda3 is bigger now
sudo pvresize /dev/sda3
  Physical volume "/dev/sda3" changed
  1 physical volume(s) resized or updated / 0 physical volume(s) not resized
# The volume group should now show more free space
sudo vgs
  VG        #PV #LV #SN Attr   VSize   VFree
  ubuntu-vg   1   1   0 wz--n- <78.00g 49.00g

VSize went from about 58G to about 78G, and VFree went from about 29G to 49G: the 29G the installer left unallocated plus the 20G just added. This is the moment the new space becomes usable. The < in front of 78.00g only means the value is slightly under 78 GiB after LVM metadata.


Step 5. Grow the logical volume

Now give part of that free space to the logical volume that holds /.

# Add exactly 20 GiB, the amount added to the VMDK, to the root logical volume
sudo lvextend -L +20G /dev/ubuntu-vg/ubuntu-lv
  Size of logical volume ubuntu-vg/ubuntu-lv changed from <29.00 GiB (7423 extents) to <49.00 GiB (12543 extents).
  Logical volume ubuntu-vg/ubuntu-lv successfully resized.

-L +20G means "add 20 GiB to the current size"; without the plus sign, -L 20G would mean "make it 20 GiB in total" and LVM would refuse because that is a shrink. The lab adds exactly what vSphere added and leaves the installer's 29G in reserve, which sudo vgs now shows as VFree. To take everything that is free instead, use -l +100%FREE, with a lowercase l because that value is counted in extents rather than in bytes.

Warning: /dev/ubuntu-vg/ubuntu-lv and /dev/mapper/ubuntu--vg-ubuntu--lv are the same logical volume under two names. The /dev/mapper form doubles every hyphen inside a name so it can use a single hyphen between the volume group and the volume. lsblk and df print the mapper form; the /dev/ubuntu-vg/ubuntu-lv form is easier to type, and both work in every command on this page.

Step 6. Grow the ext4 filesystem

The logical volume is 49G, but the ext4 filesystem inside it was built for 29G and still thinks it is that size. resize2fs grows it to fill the volume.

# With no size argument, resize2fs grows ext4 to fill the whole device.
# It takes the DEVICE, not the mount point /.
sudo resize2fs /dev/ubuntu-vg/ubuntu-lv
resize2fs 1.47.2 (1-Jan-2025)
Filesystem at /dev/ubuntu-vg/ubuntu-lv is mounted on /; on-line resizing required
old_desc_blocks = 4, new_desc_blocks = 7
The filesystem on /dev/ubuntu-vg/ubuntu-lv is now 12844032 (4k) blocks long.

"On-line resizing required" is information, not an error: ext4 can grow while mounted, and resize2fs did so. 12844032 blocks of 4 KiB is exactly the size of the logical volume, just under 49 GiB. The first line shows the version of the tools; yours may differ.

Note: sudo lvextend -r -L +20G /dev/ubuntu-vg/ubuntu-lv does steps 5 and 6 in one command: -r runs the filesystem resize for you. Use it once the sequence is familiar. The lab keeps them separate so you can see which layer does what.

Step 7. Verify every layer

Run the same commands as at the start. Every layer should now report a new size.

lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
df -hT /
sudo lvs
sudo vgs
Ubuntu Server terminal showing pvresize /dev/sda3, vgs with 49.00g free in ubuntu-vg, lvextend -L +20G growing ubuntu-lv from 29 to 49 GiB, resize2fs online resize, and df -hT showing the root filesystem at 49G and 56 percent used
Steps 4 to 7 on one screen: pvresize, vgs, lvextend, resize2fs, then lsblk and df with / at 49G and 56%.
NAME                        SIZE TYPE FSTYPE      MOUNTPOINTS
sda                          80G disk
├─sda1                        1M part
├─sda2                        2G part ext4        /boot
└─sda3                       78G part LVM2_member
  └─ubuntu--vg-ubuntu--lv    49G lvm  ext4        /

Filesystem                        Type  Size  Used Avail Use% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv ext4   49G   26G   21G  56% /
LayerBeforeAfterGrown by
Virtual disk sda60G80GvSphere
Partition sda358G78Ggrowpart
Volume group ubuntu-vgabout 58G, 29G freeabout 78G, 29G still freepvresize
Logical volume ubuntu-lv29G49Glvextend
Filesystem on /29G, 94% used49G, 56% usedresize2fs
Result: The same 26G of data now sits in a 49G filesystem, with / mounted the whole time. If df still shows the old size, read the layers from the bottom up: the first one still showing an old number is the step to repeat.

Step 8. Clean up the lab

The filler file did its job. Remove it and check the space comes back.

# Remove the file created for the lab
sudo rm /var/tmp/lab-fill.img

# Used should drop by the size of the file
df -hT /

The VM is now a clean Ubuntu Server with an 80 GB disk and a 49G root. To repeat the lab from the beginning, the quickest route is a snapshot taken right after installation, reverted before step 1 and deleted once you are done, since a snapshot blocks the disk extend itself.


When there is no LVM

Some Ubuntu cloud images put ext4 straight on a partition, with no volume group in between. lsblk shows it immediately: the part line carries the / mount point and there is no lvm line under it. The chain is then shorter: extend the VMDK, grow the partition, grow the filesystem.

# Confirm there is no lvm layer: / is mounted directly on a partition
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS

# Grow the partition that holds /, here partition 1, then the filesystem on it
sudo growpart /dev/sda 1
sudo resize2fs /dev/sda1
Note: Without LVM there is no pvresize and no lvextend, and there is also no way to add space from a second disk later. That is the practical argument for LVM on a server that may grow again.

What goes wrong

SymptomCauseFix
Disk size field greyed out in vSphereThe VM has snapshotsDelete or consolidate, then extend
lsblk still shows sda at 60GThe guest has not seen the new sizeRescan sda or power cycle
growpart: command not foundcloud-guest-utils is missingInstall it where apt can reach a mirror
NOCHANGE: partition 3 ... cannot be grownNo free space right after sda3Check lsblk, then step 2
lvextend reports insufficient free spacepvresize was skippedRun step 4, check vgs
resize2fs says Nothing to do!Volume not extended, or wrong deviceCheck lvs, copy the path from lsblk
Every layer is bigger except dfStep 6 did not runresize2fs /dev/ubuntu-vg/ubuntu-lv
netplan try rolled backNot confirmed in 120 secondsRun it again and press Enter
SSH: Connection refusedSSH server not runningsudo systemctl enable --now ssh

Tips and limitations

  • Growing is online and safe. Shrinking is not: ext4 can only be shrunk while unmounted, and a root filesystem cannot be unmounted on a running system. Plan sizes upward only.
  • Run sudo vgs before asking for a bigger VMDK. On a default Ubuntu Server install the volume group often has unallocated space already, and lvextend plus resize2fs is the whole fix.
  • Take a backup or a snapshot before the first time you do this on a production VM, and remove the snapshot afterwards so the next extend is not blocked by it.
  • Extending the VMDK consumes datastore space immediately on a thick disk and gradually on a thin one. Check the datastore, not just the guest.
  • On a machine with several disks, match the /dev/sdX to the VMDK you extended by size before running growpart. Growing the wrong partition table is not something LVM will warn you about.
  • An MBR disk cannot go past 2 TB. If the disk needs to grow beyond that, it has to be converted to GPT or the space added as a second disk.
  • /boot on sda2 is a plain partition outside LVM, and it is rarely the one that fills up. If it does, the fix is removing old kernels, not resizing.

Official documentation


  • PowerCLI Command Builder - build the PowerCLI call when you would rather extend the disk from a script than from the UI.

Five cheat sheets, one PDF

Subnet masks, PowerShell, Linux commands, HTTP status codes and the ESXi command line - one page each, free to keep. Leave an address and it arrives in a minute.