Skip to content

LVM & storage

Scope: the course page's storage section builds the PV→VG→LV→filesystem stack, grows a logical volume and its XFS filesystem live, and persists the mount in /etc/fstab — then stops at "XFS resizes up, not down." Real hosts also need to shrink a volume, snapshot one before touching it, overcommit storage on purpose, or tear the whole stack down — and each of those behaves nothing like the grow you just learned. This page covers shrinking (and why it means leaving XFS), snapshots for a consistent point-in-time copy, thin provisioning for overcommitted pools, and the safe order for growing across a new disk or removing the stack entirely.

TL;DR — in 30 seconds:

  • Growing a volume across a new disk is always vgextend then lvextend then xfs_growfs — the Volume Group has to own the extra space before any Logical Volume can draw from it.
  • XFS can't shrink, ever — a volume that needs to get smaller has to be ext4, and the filesystem must shrink before the logical volume, never after.
  • An LVM snapshot only stores changed blocks (copy-on-write), so it's a fraction of the origin's size — but it goes invalid if it fills up before you're done with it.

1. The stack, as one picture

Every layer only exists because the one below it agreed to be carved up:

flowchart TB
    D1["Physical block device\n/dev/sdb"]
    D2["Physical block device\n/dev/sdc"]
    PV1["Physical Volume\npvcreate"]
    PV2["Physical Volume\npvcreate"]
    VG["Volume Group\nvgcreate — a pool"]
    LV1["Logical Volume\nlvcreate — /var/lib/containers"]
    LV2["Logical Volume\nlvcreate — swap"]
    FS["Filesystem (XFS)\nmkfs.xfs"]
    MNT["Mount point\n/var/lib/containers"]

    D1 --> PV1
    D2 --> PV2
    PV1 --> VG
    PV2 --> VG
    VG --> LV1
    VG --> LV2
    LV1 --> FS
    FS --> MNT

A Volume Group pools Physical Volumes from one or more disks; a Logical Volume is a slice of that pool that behaves like a normal block device; the filesystem is the last layer that turns bytes into files. Nothing above a layer can outlive what it depends on — you can't keep an LV once its VG is gone, and you can't keep a filesystem's data once its LV is removed.

2. Growing across a newly added disk

The course page grows an LV that already has room in its VG. The other common case is the VG itself running low — you add a disk first, then extend:

sudo pvcreate /dev/sdc                        # new disk becomes a Physical Volume
sudo vgextend rhel /dev/sdc                   # the VG's pool now includes it
sudo lvextend -L +100G /dev/rhel/data         # carve the extra space into the LV
sudo xfs_growfs /data                         # grow the filesystem to fill the LV

The order matters: a disk must join the Volume Group (vgextend) before any Logical Volume can draw space from it (lvextend), and the filesystem only sees the extra room once xfs_growfs (or the equivalent for another filesystem) runs.

3. Shrinking — why it means leaving XFS

XFS was designed to grow live and cannot shrink, at all, online or offline — confirmed in Red Hat's own LVM documentation. If a logical volume genuinely needs to get smaller — a partition was oversized, disk is being reclaimed for another LV — the filesystem on it has to be one that supports shrinking, most commonly ext4. The order is the reverse of growing, and getting it backwards destroys data:

sudo umount /data                                   # 1. unload the filesystem first
sudo e2fsck -f /dev/rhel/data                       # 2. force-check; shrinking an unclean fs is unsafe
sudo resize2fs /dev/rhel/data 200G                  # 3. shrink the FILESYSTEM to the new, smaller size
sudo lvreduce -L 200G /dev/rhel/data                # 4. THEN shrink the LOGICAL VOLUME to match
sudo e2fsck -f /dev/rhel/data                       # 5. re-check
sudo mount /data

Note: shrink the filesystem before the logical volume, never the other way round. lvreduce only knows block ranges, not which blocks the filesystem is actually using — if the LV shrinks first, any of the filesystem's live data sitting past the new, smaller boundary is gone.

Because of this asymmetry, a RHEL host that anticipates needing to shrink a volume later is usually built on ext4 for that volume from the start, even though XFS is the RHEL default.

4. Snapshots — a consistent copy without stopping the volume

An LVM snapshot is a copy-on-write logical volume that freezes the origin LV's contents at the moment it was taken, while the origin keeps accepting writes:

sudo lvcreate -L 10G -s -n data-snap /dev/rhel/data   # -s: snapshot; 10G is space for CHANGED blocks only
sudo mount -o ro /dev/rhel/data-snap /mnt/snap        # back up, inspect, or diff from here
sudo umount /mnt/snap
sudo lvremove /dev/rhel/data-snap                     # done — discard the snapshot

The snapshot only stores blocks that have changed since it was taken (via copy-on-write), which is why its allocated size is a fraction of the origin's, not a full copy. That also means a snapshot that runs out of its own allocated space while the origin is being written to heavily gets marked invalid and is lost — size it for the snapshot's expected lifetime and the origin's write rate during that window, or grow it live with lvextend if it's filling up.

The everyday use for a snapshot is a consistent backup of a live volume: take the snapshot, back up from the read-only mount (the origin keeps serving production traffic unaffected), then discard it.

5. Thin provisioning — pools that overcommit on purpose

Ordinary LVM allocates real space to a Logical Volume the moment you create it. A thin pool instead lets you create Logical Volumes whose declared size is bigger than the space actually reserved, allocating real blocks only as data is written:

sudo lvcreate -L 500G -T rhel/thinpool                       # a 500G pool, physically backed
sudo lvcreate -V 200G -T rhel/thinpool -n app1               # "virtual" 200G volume from it
sudo lvcreate -V 200G -T rhel/thinpool -n app2                # another 200G volume, same pool
sudo lvs -o+lv_size,data_percent                              # data_percent: how full the pool really is

Two 200G volumes from a 500G pool are only safe because most workloads never write anywhere near their declared size — the trade-off is that the pool can run out of real space even though every volume in it still reports free room, which silently stalls writes. A thin-provisioned host needs its pool's data_percent monitored and alerted on; this is the same overcommit trade-off cloud block storage makes, just one layer lower.

6. Removing the stack — the safe order

Tearing down cleanly is the mirror of building up, one layer at a time, always from the top:

sudo umount /data                       # 1. unmount the filesystem
sudo lvremove /dev/rhel/data            # 2. remove the Logical Volume
sudo vgreduce rhel /dev/sdc             # 3. (optional) drop one disk from the Volume Group
sudo vgremove rhel                      # 4. remove the Volume Group once no LVs remain
sudo pvremove /dev/sdb /dev/sdc         # 5. wipe the LVM metadata off the Physical Volumes

Skipping a step in the wrong direction — trying to pvremove a disk still inside a Volume Group, or vgremove a Volume Group that still has Logical Volumes on it — is rejected by LVM rather than silently breaking anything, but knowing the order means never fighting it.

7. How this connects

The course page's PV→VG→LV→filesystem stack and one live grow is the 80% you'll do on almost every RHEL host. This page is what to reach for once a volume needs to get smaller instead of bigger, once you need a consistent copy of a volume that's still being written to, once storage needs to be overcommitted on purpose for a fleet of similarly-sized volumes, or once a host is being decommissioned and the stack needs to come apart cleanly.

Common gotchas

  • Shrinking the logical volume before the filesystem. Fix: always resize the filesystem down first, then lvreducelvreduce only knows block ranges, not which blocks the filesystem is using, so shrinking the LV first destroys any data past the new boundary.
  • Trying to shrink an XFS volume at all. Fix: XFS can only grow — a volume that needs to shrink has to be ext4 (or rebuilt from a backup onto ext4); there's no XFS shrink operation to reach for.
  • Sizing a snapshot for the origin's total size instead of its expected change rate. Fix: a snapshot only needs space for blocks that change while it exists — undersizing it for a long-lived, heavily-written origin is what makes it fill up and go invalid.
  • Running vgremove/pvremove out of order and assuming it just works. Fix: LVM rejects removing a VG that still has LVs, or a PV still inside a VG — tear down top-to-bottom (unmount → lvremove → vgreduce/vgremove → pvremove) and it never fights you.
Check yourself — you shrink a logical volume with lvreduce first, then try to run resize2fs to match. What went wrong, and what should the order have been?

Shrinking the LV first can destroy data — lvreduce only knows block ranges, not which blocks the filesystem is actually using, so any live data past the new, smaller boundary is gone. The filesystem must be shrunk with resize2fs before lvreduce touches the logical volume.

Check yourself — an LVM snapshot you took hours ago suddenly shows as invalid, and you can no longer mount it. What most likely happened?

It ran out of its own allocated copy-on-write space — a snapshot only reserves room for blocks that change after it's taken, not a full copy, so heavy writes to the origin (or an undersized snapshot) can fill that space and mark it invalid.

You can defend this when you can explain why growing needs vgextend before lvextend, why shrinking must resize the filesystem before the logical volume (and why that rules out XFS), what a snapshot actually stores and what happens if it fills up, what a thin pool trades away for its overcommit, and the order to remove an LV/VG/PV stack without LVM rejecting you.