159 lines
5.8 KiB
Markdown
159 lines
5.8 KiB
Markdown
---
|
||
title: "Growing an LVM Volume by Absorbing Another Disk"
|
||
domain: linux
|
||
category: storage
|
||
tags: [lvm, lvextend, vgextend, pvcreate, resize2fs, ext4, storage, disk, homelab]
|
||
status: published
|
||
created: 2026-06-17
|
||
updated: 2026-06-17
|
||
---
|
||
|
||
# Growing an LVM Volume by Absorbing Another Disk
|
||
|
||
When an LVM-backed filesystem fills up and its volume group (VG) has no free
|
||
extents, you can grow it by adding a second physical disk as a new physical
|
||
volume (PV), extending the VG onto it, then extending the logical volume (LV)
|
||
and its filesystem. With ext4 this can be done **online** — no unmount, no
|
||
downtime for the volume being grown.
|
||
|
||
This guide covers the common case where the disk you want to absorb is currently
|
||
in use by its own LVM volume (you must evacuate and tear that down first), and
|
||
the precautions that keep it safe.
|
||
|
||
> [!warning] This enlarges your failure domain
|
||
> A single LV spanning two disks linearly (the default — no RAID/mirror) means
|
||
> **losing either disk loses the entire volume.** ext4 has no parity. Only do
|
||
> this for data you can rebuild, or layer redundancy (mdadm/LVM RAID) underneath.
|
||
> Back up anything irreplaceable first.
|
||
|
||
## The Short Answer
|
||
|
||
If the target disk (`/dev/sdX`) is already empty and unused:
|
||
|
||
```bash
|
||
sudo pvcreate /dev/sdX
|
||
sudo vgextend myvg /dev/sdX
|
||
sudo lvextend -l +100%FREE /dev/myvg/mylv
|
||
sudo resize2fs /dev/mapper/myvg-mylv # ext4, online; use xfs_growfs for XFS
|
||
```
|
||
|
||
The rest of this article handles the harder case: the target disk is currently
|
||
holding its own LVM volume with data on it.
|
||
|
||
## Step-by-Step
|
||
|
||
### 1. Survey the current layout
|
||
|
||
```bash
|
||
sudo pvs # physical volumes → which VG each belongs to
|
||
sudo vgs # volume groups, free extents (VFree)
|
||
sudo lvs # logical volumes and sizes
|
||
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT
|
||
df -h
|
||
```
|
||
|
||
Confirm:
|
||
|
||
- The VG you want to grow (`myvg`) has `0` `VFree` (that's why you're here).
|
||
- The disk you want to absorb (`/dev/sdX`) is a **standalone** PV — not a member
|
||
of an mdadm array, a mergerfs branch, or a SnapRAID parity disk. Repurposing a
|
||
disk that something else depends on will break that thing silently.
|
||
|
||
### 2. Evacuate the disk you're about to absorb
|
||
|
||
Anything on the target disk will be **destroyed**. Move it somewhere with room to
|
||
spare, then prove the copy is intact before you trust it.
|
||
|
||
```bash
|
||
# Copy preserving permissions/timestamps
|
||
sudo rsync -a /mnt/olddisk/important /destination/with/space/
|
||
|
||
# Verify byte-for-byte — empty output + exit code 0 means identical
|
||
sudo diff -rq /mnt/olddisk/important /destination/with/space/important && echo OK
|
||
```
|
||
|
||
For large trees the `diff -rq` (full byte comparison) is slow but is the
|
||
authoritative check — don't skip it before the destructive phase. If an
|
||
application tracks files by path (databases, media servers), update its path
|
||
references to the new location *now*, while the old copy still exists as a
|
||
fallback.
|
||
|
||
### 3. Unmount and remove the old disk from fstab
|
||
|
||
```bash
|
||
sudo fuser -m /mnt/olddisk # confirm nothing holds it open
|
||
sudo umount /mnt/olddisk
|
||
mountpoint -q /mnt/olddisk && echo "STILL MOUNTED" || echo "unmounted"
|
||
|
||
sudo cp /etc/fstab /etc/fstab.bak-$(date +%Y%m%d) # always back up fstab
|
||
sudo sed -i '/olddisk/d' /etc/fstab # remove the stale entry
|
||
grep olddisk /etc/fstab || echo "fstab line gone"
|
||
```
|
||
|
||
> [!tip] Verify your `sed` pattern only matches the line you mean
|
||
> A too-broad pattern can delete the wrong fstab entry. Check the file before and
|
||
> after, and keep the backup until you've confirmed the system still boots.
|
||
|
||
### 4. Tear down the old disk's LVM
|
||
|
||
```bash
|
||
sudo lvremove -y /dev/oldvg/oldlv
|
||
sudo vgremove -y oldvg
|
||
sudo pvremove -y /dev/sdX # wipes the LVM label off the disk
|
||
```
|
||
|
||
This is the point of no return for the old disk's data — which is why steps 2–3
|
||
verified the copy first.
|
||
|
||
### 5. Add the disk to the target VG and extend
|
||
|
||
```bash
|
||
sudo pvcreate -y /dev/sdX
|
||
sudo vgextend myvg /dev/sdX
|
||
sudo lvextend -l +100%FREE /dev/myvg/mylv
|
||
```
|
||
|
||
`lvs`/`vgs` should now show the LV grown to span both disks and `0` free extents.
|
||
|
||
### 6. Grow the filesystem (online)
|
||
|
||
```bash
|
||
# ext4 — works while mounted
|
||
sudo resize2fs /dev/mapper/myvg-mylv
|
||
|
||
# XFS — grows online too, but takes the mountpoint, not the device
|
||
sudo xfs_growfs /mountpoint
|
||
```
|
||
|
||
`resize2fs` is idempotent — if it gets interrupted, just run it again; it reports
|
||
"Nothing to do!" once the filesystem already fills the LV.
|
||
|
||
### 7. Verify
|
||
|
||
```bash
|
||
df -h /mountpoint # should reflect the new larger size
|
||
sudo pvs # /dev/sdX now listed under myvg
|
||
sudo vgs myvg # two PVs, larger VSize
|
||
```
|
||
|
||
## Notes & Gotchas
|
||
|
||
- **Online resize works for the volume being grown, not the one being removed.**
|
||
The disk you absorb must be unmounted and torn down; the destination LV stays
|
||
mounted throughout.
|
||
- **`resize2fs` interruption is safe.** ext4 online resize is journaled; re-run it.
|
||
- **macOS cruft on evacuated disks.** Trees touched by macOS often carry
|
||
`._*` AppleDouble files and `.DS_Store` — harmless to drop, but they inflate
|
||
file counts in `diff`/`rsync` output. Don't mistake them for real data.
|
||
- **Check SMART on a disk you're promoting into a bigger role.** A disk with a
|
||
pending-sector history is riskier once it's in the critical path for a whole
|
||
multi-disk volume than it was holding a small isolated one.
|
||
- **Mountpoint cleanup.** After the old disk is gone, its former mountpoint
|
||
directory may reappear (it was shadowed by the mount). `rmdir` it if empty.
|
||
Note `ls -A` exits `0` on an empty directory, so don't gate cleanup on its exit
|
||
status — test contents explicitly.
|
||
|
||
## Related
|
||
|
||
- [SnapRAID & MergerFS Storage Setup](snapraid-mergerfs-setup.md) — add redundancy/parity instead of a linear span
|
||
- [mdadm — Rebuilding a RAID Array After Reinstall](mdadm-raid-rebuild.md)
|