LVM Fundamentals
Physical Volumes, Volume Groups, and Logical Volumes
What LVM Is
LVM (Logical Volume Manager) is an abstraction layer that sits between raw block devices and the filesystems running on top of them. Instead of binding a filesystem directly to a partition, LVM pools one or more block devices together and carves out virtual block devices of arbitrary size from that pool.
Why It Exists
Partitions are static. Resizing them is fragile and usually offline. LVM makes the mapping from physical capacity to exposed volumes a runtime concern:
- Resize volumes online — grow (and sometimes shrink) while in use
- Add / remove disks without re-partitioning the filesystem
- Move data between disks while applications keep writing (
pvmove) - Snapshot at the block layer for backups and rollback
- Thin-provision — allocate on demand, over-commit capacity (see Thin Provisioning & Snapshots)
- RAID via dm-raid with the same kernel code as mdadm
Note
Under the hood: LVM is built entirely on device-mapper.
Every LV is a dm device with a specific target (linear, striped, raid, thin, mirror, cache…).
See Device Mapper for the primitives
— this page focuses on how LVM composes them into a usable system.
The Three-Level Model
LVM has exactly three logical layers. Master the boundaries between them and the rest of the command surface follows.
/dev/vg/lv — virtual block devicePhysical Volume (PV)
A PV is any block device that has been initialized for LVM use. It can be
a whole disk, a partition, an mdadm RAID array, a LUKS mapping, an iSCSI LUN,
anything that exposes a stable block interface. pvcreate writes a
small metadata header (the LVM label) at the start of the device and divides the rest
into fixed-size Physical Extents (PEs).
root@host:~# pvcreate /dev/sdb1
Physical volume "/dev/sdb1" successfully created.
root@host:~# pvs
PV VG Fmt Attr PSize PFree
/dev/sdb1 data lvm2 a-- <1.82t 0
/dev/sdc1 data lvm2 a-- <1.82t 120.00g
/dev/sdd1 lvm2 --- 931.51g 931.51gVolume Group (VG)
A VG is a pool built from one or more PVs. Once a PV is in a VG, its PEs
become indistinguishable members of a single allocation pool — the VG doesn’t
care which disk they live on (unless you tell it to care, via allocation policies
or striping). The VG is the unit you name meaningfully: data, vg_ssd,
pve, nvme0…
root@host:~# vgcreate data /dev/sdb1 /dev/sdc1
Volume group "data" successfully created
root@host:~# vgs
VG #PV #LV #SN Attr VSize VFree
data 2 0 0 wz--n- 3.64t 3.64tLogical Volume (LV)
An LV is a virtual block device allocated from a VG. It has a size, a type
(linear, striped, raid, thin, mirror, cache), and appears in /dev as both
/dev/<vg>/<lv> (symlink) and /dev/mapper/<vg>-<lv> (the actual dm node).
root@host:~# lvcreate -L 100G -n mydata data
Logical volume "mydata" created.
root@host:~# ls -l /dev/data/mydata /dev/mapper/data-mydata
lrwxrwxrwx 1 root root 7 Apr 20 10:00 /dev/data/mydata -> ../dm-3
lrwxrwxrwx 1 root root 7 Apr 20 10:00 /dev/mapper/data-mydata -> ../dm-3
root@host:~# mkfs.xfs /dev/data/mydata && mount /dev/data/mydata /dataPhysical Extents (PE)
The PE is LVM’s unit of allocation. Default size is 4 MiB
(configurable at vgcreate time with -s). Every allocation decision —
creating an LV, extending one, moving data — happens in whole PEs.
- A 100 GiB LV = 25,600 PEs at the default 4 MiB
- The VG tracks the free PE count across all member PVs
- Linear LVs claim contiguous PEs (on a single PV when possible, spanning PVs when needed)
- Striped LVs interleave PEs across PVs in a round-robin pattern
Think of the VG as a grid of PE cells distributed across PVs. An LV is a set of cells marked as belonging to it. The mapping lives in the VG metadata — not in any on-disk structure of the LV itself.
PE grid — simplified view of a VG with 2 PVs
PV1 (/dev/sdb1) PV2 (/dev/sdc1)
┌───┬───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┐
│ A │ A │ A │ A │ B │ B │ │ B │ B │ C │ C │ . │ . │
├───┼───┼───┼───┼───┼───┤ ├───┼───┼───┼───┼───┼───┤
│ A │ A │ A │ A │ . │ . │ │ . │ . │ C │ C │ . │ . │
└───┴───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┴───┘
A = LV "mydata" (8 PEs, linear on PV1)
B = LV "logs" (4 PEs, spans PV1 → PV2, linear)
C = LV "scratch" (4 PEs, on PV2)
. = free PELV Types
The LV type chooses which dm target backs the volume. The default
is linear; everything else you ask for explicitly.
| Type | dm target | Purpose | Create |
|---|---|---|---|
| linear (default) | dm-linear | Contiguous PE allocation, may span multiple PVs | lvcreate -L 100G -n lv vg |
| striped | dm-stripe | Interleave PEs across N PVs for parallel I/O | lvcreate -L 100G -i 4 -I 64 -n lv vg |
| mirror (legacy) | dm-mirror | Synchronous copies — prefer raid1 | lvcreate --type mirror -m1 -L 100G… |
| raid1/4/5/6/10 | dm-raid (MD code) | Modern RAID levels via the kernel MD stack | lvcreate --type raid5 -i 3 -L 300G… |
| thin | dm-thin | Allocate on demand from a thin pool | See thin-provisioning-snapshots.html |
| cache | dm-cache | Front a slow LV with an SSD-backed cache pool | See thin/snapshots page |
| snapshot (traditional) | dm-snapshot | Short-lived CoW overlay of an origin LV | lvcreate -s -L 10G -n snap vg/origin |
Tip
Striping tuning: -i N is the stripe count (number of PVs),
-I stripesize is in KiB (default 64). Choose a stripe size that matches
the dominant I/O unit — larger for sequential, smaller for random.
LVM Metadata
LVM metadata describes the entire VG: PV UUIDs, PE allocations, LV names, types, and extent maps. It is stored on every PV in the VG by default (redundant copies), in a human-readable text format inside the first few MiB of each PV. When you run any LVM command, it reads metadata from a quorum of PVs and writes updates back atomically.
- Number of copies: one per PV by default; tunable with
--metadatacopies - On-disk format: plain text (looks like a config file)
- Backups: every change is also dumped to
/etc/lvm/backup/<vg>and archives to/etc/lvm/archive/
root@host:~# vgcfgbackup -f /tmp/vg-data.txt data
Volume group "data" successfully backed up.
root@host:~# pvck --dump metadata /dev/sdb1 | head -20
data {
id = "aB3cD4-ef5G-..."
seqno = 17
format = "lvm2"
status = ["RESIZEABLE", "READ", "WRITE"]
extent_size = 8192 # 4 MiB in sectors
physical_volumes {
pv0 { id = "..." device = "/dev/sdb1" ... }
pv1 { id = "..." device = "/dev/sdc1" ... }
}
logical_volumes {
mydata { segment_count = 1 ... }
}
}Tip
Recovery: a wiped PV header can often be restored with
vgcfgrestore -f /etc/lvm/archive/<vg>_NNNN.vg <vg> as long as the
PV’s data area hasn’t been overwritten.
Command Cheat Sheet
LVM commands follow a strict verb-per-layer naming scheme. Once you see the pattern, the whole interface memorises itself.
| Layer | Inspect | Create / Modify | Activate / Move / Remove |
|---|---|---|---|
| PV | pvs, pvdisplay, pvck |
pvcreate, pvresize, pvchange |
pvmove, pvremove |
| VG | vgs, vgdisplay, vgck |
vgcreate, vgextend, vgreduce, vgrename |
vgchange -a y|n, vgremove |
| LV | lvs, lvdisplay |
lvcreate, lvextend, lvreduce, lvresize, lvrename, lvconvert |
lvchange -a y|n, lvremove |
Tip
Shortcut flags: -o +<field> adds columns to pvs/vgs/lvs.
Try lvs -o +devices,stripes,lv_attr,lv_layout for a denser view.
Online Resize
Growing a volume
Two steps: extend the LV at the block layer, then grow the filesystem. LVM can do both at once
with -r (--resizefs).
root@host:~# lvextend -L +50G /dev/data/mydata
Size of logical volume data/mydata changed from 100.00 GiB to 150.00 GiB.
Logical volume data/mydata successfully resized.
root@host:~# # ext4
root@host:~# resize2fs /dev/data/mydata
root@host:~# # xfs (must be mounted)
root@host:~# xfs_growfs /data
root@host:~# # one-shot: grow LV and filesystem together
root@host:~# lvextend -r -L +50G /dev/data/mydataShrinking a volume (ext4 only)
Warning
XFS cannot be shrunk. Full stop. If you need a smaller volume, xfsdump
to another volume, recreate at the target size, xfsrestore. btrfs supports shrink
natively (btrfs filesystem resize).
Warning
Always back up first. Shrink is the one LVM operation that can easily lose data if the filesystem ends up smaller than the LV. The rule is: filesystem size ≤ LV size, never the other way around.
root@host:~# umount /data
root@host:~# e2fsck -f /dev/data/mydata # required before resize2fs shrink
root@host:~# resize2fs -p /dev/data/mydata 80G # shrink FS first
root@host:~# lvreduce -L 80G /dev/data/mydata # then shrink LV
root@host:~# e2fsck -f /dev/data/mydata
root@host:~# mount /dev/data/mydata /dataOnline Data Migration: pvmove
pvmove relocates PEs from one PV to another while the LV stays online.
It uses a temporary dm-mirror to duplicate writes to both source and destination
until all PEs are copied, then atomically switches the LV’s extent map to point at the
new PV. Applications see no interruption.
Typical uses: retiring a failing disk, swapping to a larger drive, consolidating data
onto faster storage, evacuating a PV before vgreduce.
root@host:~# # move everything off /dev/sdb1 onto /dev/sdd1
root@host:~# pvmove /dev/sdb1 /dev/sdd1
/dev/sdb1: Moved: 0.12%
/dev/sdb1: Moved: 17.34%
/dev/sdb1: Moved: 54.81%
/dev/sdb1: Moved: 100.00%
root@host:~# # now safe to drop the old PV
root@host:~# vgreduce data /dev/sdb1
root@host:~# pvremove /dev/sdb1Tip
Resumable: pvmove is interruptible. If the host reboots mid-move,
run pvmove with no args on the next boot and it picks up where it left off.
Adding a Disk to a VG
The canonical growth workflow: initialise the new disk as a PV, add it to the VG, then extend the LV to consume the new free space (and grow the filesystem).
root@host:~# pvcreate /dev/sde
root@host:~# vgextend data /dev/sde
root@host:~# vgs
VG #PV #LV #SN Attr VSize VFree
data 3 4 0 wz--n- 5.46t 1.82t
root@host:~# lvextend -r -l +100%FREE /dev/data/mydataNote
Whole disk vs. partition: using the whole disk (/dev/sde)
as a PV is simpler and avoids alignment pitfalls. Partitioning first
(/dev/sde1) is needed only if something else will share the disk, or if you
want a bootable GPT for BIOS constraints.
Activation & Boot
LVs are inactive until LVM creates the dm nodes for them. On boot, the
lvm2-*.service / lvm2-activation-*.service units (invoked by udev and
systemd generators) scan available PVs and activate any VG they can assemble.
root@host:~# vgchange -a y data # activate all LVs in vg "data"
root@host:~# vgchange -a n data # deactivate
root@host:~# vgchange --activationmode partial # activate a degraded RAID LV/boot on LVM
GRUB2 can read LVM (including thin pools on recent versions) and boot a kernel from an LV.
In practice most installers still put /boot on a plain partition — it keeps
recovery simple (any Linux live image can mount it), avoids coupling GRUB to LVM quirks,
and makes Secure Boot / boot-chain updates easier.
LVM RAID vs. mdadm
LVM RAID and mdadm both sit on the same kernel code — the MD/dm-raid personality. There is no performance or reliability difference. The distinction is operational:
- RAID is an LV type; add/remove members with LVM commands
- Metadata lives in the VG alongside LV maps
- Integrates with
pvmove, thin pools, caching lvcreate --type raid5 -i 3 -L 300G -n rlv vg
- Standalone subsystem — exposes
/dev/mdX - Metadata at the start/end of each member (0.90, 1.x)
- Simpler for plain block-level RAID with no LVM on top
- Most distro installers use mdadm + LVM (md as a PV)
Traditional (Non-thin) Snapshots
The classic LVM snapshot is a copy-on-write overlay on top of an origin LV. You allocate a separate LV to hold the CoW exception store; LVM intercepts writes to the origin and copies the old block into the snapshot LV before overwriting. Reading from the snapshot returns origin blocks unless they’ve been overwritten — in which case the original value comes from the snapshot’s store.
root@host:~# lvcreate -L 10G -s -n mydata_snap /dev/data/mydata
Logical volume "mydata_snap" created.
root@host:~# mount -o ro /dev/data/mydata_snap /mnt/snap
root@host:~# tar czf /backup/snap.tgz /mnt/snap
root@host:~# umount /mnt/snap && lvremove /dev/data/mydata_snapWarning
The snapshot LV can fill up. Its size must accommodate every block changed
on the origin while the snapshot lives. If the exception store fills, the snapshot becomes
invalid and is dropped — any reader of it gets I/O errors. Monitor
lvs -o name,snap_percent.
Warning
Performance cost: every origin write now triggers a read-old-block + write-to-snapshot before the actual write. Fine for minutes-to-hours, not for long-lived snapshots. For anything persistent or at scale, use thin snapshots instead.
Common Storage Stacks
LVM plays well with mdadm, LUKS, and anything that looks like a block device. The ordering determines what each layer can do.
Stack A — Classic full-disk encryption with RAID
disks → mdadm (RAID1/5/6) → LUKS → LVM (PV/VG/LVs) → filesystem
• mdadm handles redundancy below encryption (one LUKS unlock for the array)
• LVM carves out volumes inside the encrypted container
• Each filesystem is on its own LV, still shares the single unlockStack B — Per-volume encryption
disks → LVM (PV/VG) → LUKS (per LV) → filesystem
• Each LV can be encrypted with its own key
• Resizing is trickier (LUKS header has fixed-size slots; cryptsetup resize required)
• Useful when only some volumes need encryptionStack C — Proxmox default on a single disk
disk → partitions → LVM-thin pool → raw LVs for VMs → filesystems inside the VM
• /boot on its own partition; root on an LV
• data VG "pve" uses a thin pool; each VM disk is a thin LV in that pool
• Snapshots / clones of VM disks are thin snapshotsTip
Order matters for flexibility: put LVM above LUKS if you want one unlock to expose many volumes, below LUKS if you want to resize the unlocked container easily. Put LVM above mdadm so the PV is the md device — redundancy stays invisible to LVM.