Logical Volume Management in Linux (RHEL 9)
A Hands-On Guide with RHEL 9
Logical Volume Management in Linux (RHEL 9)

This article is a continuation of my earlier piece, **“Disk Partitioning in Linux”**, where I covered the fundamentals of disk partitioning from a raw empty disk to persistent filesystem with every command explaining. LVM builds directly on top of those concepts. A Physical Volume for instance is created from a partition, you would already know how to set up. If you have not gone through that article yet, I would strongly recommend starting there first. Without a solid grasp of basic partitioning, the LVM layer will feel harder to follow. Consider this Part 2 of the series, picking up right where partitioning leaves off.
You created a partition, filled it up, and now you need more space. With a traditional partition, that usually means backups, deleting, and recreating things from scratch and hoping nothing breaks along the way. There is a better way to manage storage on Linux, it is the Logical Volume Manager (LVM).
In this article, I will walk through building a real LVM setup on RHEL 9, step by step, using the actual commands and output from a live system. By the end, you will have a 5 GB logical volume mounted and working and you will see it extended to 8 GB live, with zero downtime.
What Is LVM?
Think of a traditional partition as a fixed-size bucket. Once you create it, that is its size permanently, unless you go through a risky resize or repartition process. LVM instead turns your disks into a pool of storage that you can pour into containers of any size, at any time.
LVM is built from three core layers:
- Physical Volume (PV) — a disk or partition prepared for use by LVM
- Volume Group (VG) — a storage pool formed by combining one or more PVs
- Logical Volume (LV) — a flexible “partition” carved out of that pool, which you format and mount like any other filesystem

PVs feed into a VG, and LVs are created out of that shared pool.
This layered design is what makes LVM powerful. You can grow a logical volume by simply pulling more space from the pool, no need to touch the disk layout underneath. It is available by default on RHEL, CentOS, Fedora, Ubuntu, and most other major distributions.
Prerequisites
- A Linux system — this walkthrough uses RHEL 9
- An extra unused disk or partition (here, /dev/sdb)
- Root or sudo access
Step-by-Step: Building the LVM Stack
Here is the full path from a raw disk to a usable, mounted directory.

Six steps take you from raw disk to mounted, writable storage.
Step 1 — Create a Partition with fdisk
Before touching anything, get a clear picture of what is already on the system.
[root@rhel-node-1 rishi]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 20G 0 disk
├─sda1 8:1 0 1G 0 part /boot
└─sda2 8:2 0 19G 0 part
├─rhel_vbox-root 253:0 0 17G 0 lvm /
└─rhel_vbox-swap 253:1 0 2G 0 lvm [SWAP]
sdb 8:16 0 20G 0 disk
├─sdb1 8:17 0 10G 0 part /data
sr0 11:0 1 51M 0 rom
The operating system is installed on a 20 GB disk (/dev/sda). This disk contains a 1 GB /boot partition and a second partition that is used by LVM to host the root (/) and swap logical volumes.
A second 20 GB disk (/dev/sdb) is also attached to the virtual machine. Currently, only the first 10 GB partition (/dev/sdb1) is formatted and mounted at /data. The remaining 10 GB of the disk is intentionally left unallocated and unused.
This unused disk space provides an ideal environment to demonstrate the flexibility of LVM. Throughout this walkthrough, we will use the available free space to create a new LVM Physical Volume (PV), build a Volume Group (VG), and create Logical Volumes (LVs). We will also explore one of the biggest advantages of LVM by dynamically extending storage capacity without repartitioning the entire disk.
I will start with available free space (/dev/sdb disk has 10GB unused space) instead of a fully allocated disk, you can try same steps for newly allocated disk.
Start by opening the disk in fdisk and creating a new partition:
[root@rhel-node-1 rishi]# fdisk /dev/sdb
Welcome to fdisk (util-linux 2.37.4).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Command (m for help): n
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Using default response p.
Partition number (2-4, default 2):
First sector (20973568-41943039, default 20973568):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (20973568-41943039, default 41943039): +10G
Value out of range.
Last sector, +/-sectors or +/-size{K,M,G,T,P} (20973568-41943039, default 41943039): +9G
Created a new partition 2 of type 'Linux' and of size 9 GiB.
A quick gotcha worth mentioning I first tried +10G for the size and got “Value out of range”. There was not quite that much free space left on the disk after the existing partition. Dropping to +9G worked. Always check your available space before picking a size.
Mark the Partition as Linux LVM
By default, a new partition is tagged as a generic Linux type. Change it to the Linux LVM type.
- t = change partition type
- Selected partition 2 (according to my example)
- Entered hex code 8e (alias lvm): This tags the partition as Linux LVM type instead of a generic Linux partition. This is a metadata flag telling the OS/tools this partition is meant for LVM, though it does not format anything yet.
Command (m for help): t
Partition number (1,2, default 2): 2
Hex code or alias (type L to list all): 8e
Changed type of partition 'Linux' to 'Linux LVM'.
Command (m for help): w
The partition table has been altered.
Syncing disks.
Saved the partition table to disk (“Syncing disks”). Up to this point, fdisk only holds changes in memory. The w command makes them permanent.
Now block device tree shows sdb2 as a partition with no filesystem/mount yet, sitting alongside the existing sdb1 (mounted at /data).
[root@rhel-node-1 rishi]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 20G 0 disk
├─sda1 8:1 0 1G 0 part /boot
└─sda2 8:2 0 19G 0 part
├─rhel_vbox-root 253:0 0 17G 0 lvm /
└─rhel_vbox-swap 253:1 0 2G 0 lvm [SWAP]
sdb 8:16 0 20G 0 disk
├─sdb1 8:17 0 10G 0 part /data
└─sdb2 8:18 0 9G 0 part
sr0 11:0 1 51M 0 rom
Step 2— Initialize the partition as a Physical Volume (PV)
With the partition ready, initialize it as a Physical Volume. This writes LVM metadata onto it so it can be pooled into a Volume Group.
pvcreate /dev/sdb2
Verified with:
[root@rhel-node-1 rishi]# pvs
PV VG Fmt Attr PSize PFree
/dev/sda2 rhel_vbox lvm2 a-- <19.00g 0
/dev/sdb2 lvm2 --- 9.00g 9.00g
Step 3 — Create the Volume Group
vgcreate vg_app /dev/sdb2
Created a new Volume Group named vg_app using /dev/sdb2 as its only PV. A VG is a storage pool combining one or more PVs. Logical volumes are then created out of this pool.
Verified with:
[root@rhel-node-1 rishi]# vgs
VG #PV #LV #SN Attr VSize VFree
rhel_vbox 1 2 0 wz--n- <19.00g 0
vg_app 1 0 0 wz--n- <9.00g <9.00g
Showed vg_app with ~9 GiB total, free space.
Step 4 — Create the Logical Volume
Now create a Logical Volume from the pool. This is the actual usable “partition” and unlike a real partition, it is not tied to fixed disk geometry.
lvcreate -L 5G -n lv_app vg_app
Created a 5 GiB logical volume named lv_app inside the vg_app volume group. This is the actual logical partition. You will format and mount analogous to a regular partition, but flexible in size since it comes from a VG pool rather than fixed disk geometry.
[root@rhel-node-1 rishi]# vgs
VG #PV #LV #SN Attr VSize VFree
rhel_vbox 1 2 0 wz--n- <19.00g 0
vg_app 1 1 0 wz--n- <9.00g <4.00g
[root@rhel-node-1 rishi]# lvs
LV VG Attr LSize
root rhel_vbox -wi-ao---- <17.00g
swap rhel_vbox -wi-ao---- 2.00g
lv_app vg_app -wi-a----- 5.00g
Verified with vgs (showed 4 GiB free left in vg_app) and lvs (listed lv_app at 5 GiB, alongside your existing root/swap LVs).
Step 5 — Format the LV with a filesystem
Before format the LV, need to find lv_app LV path. lvdisplay command showed full metadata for all LVs. Path (/dev/vg_app/lv_app), UUID, size, creation time, block device number, etc. Confirmed lv_app was created and available.
[root@rhel-node-1 rishi]# lvdisplay
--- Logical volume ---
LV Path /dev/vg_app/lv_app
LV Name lv_app
VG Name vg_app
LV UUID biQSVP-z52f-KQcv-Nj35-gQUA-XSJy-dP8Nf6
LV Write Access read/write
LV Creation host, time rhel-node-1, 2026-07-13 14:30:58 +0530
LV Status available
# open 0
LV Size 5.00 GiB
Current LE 1280
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:2
...
Let’s create an ext4 filesystem on the logical volume. This is what makes it usable for storing files.
💡 LVM itself is just a storage abstraction layer, without a filesystem it cannot hold data directly.
mkfs.ext4 /dev/vg_app/lv_app
Step 6 — Mount It Permanently
First, we need to find UUID of lv_appLV.
[root@rhel-node-1 rishi]# blkid
...
/dev/mapper/vg_app-lv_app: UUID="8aa5b109-da18-4385-afb4-06f8b79999fe" BLOCK_SIZE="4096" TYPE="ext4"
...
blkid command listed UUIDs and filesystem types for all block devices, confirming /dev/mapper/vg_app-lv_app now shows TYPE="ext4" with a UUID. This UUID is what you would typically use in /etc/fstab for reliable mounting.
Let’s create the empty directory that will serve as the mount point and add an entry to /etc/fstab and mount everything.
mkdir /App
vim /etc/fstab
mount -a

/etc/fstab file view after adding new entry
💡Note: Use the filesystem’s UUID (from blkid) in /etc/fstab rather than the raw device path. Because device-mapper names can occasionally shift, while UUIDs stay stable.
Verify Everything
[root@rhel-node-1 rishi]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 20G 0 disk
├─sda1 8:1 0 1G 0 part /boot
└─sda2 8:2 0 19G 0 part
├─rhel_vbox-root 253:0 0 17G 0 lvm /
└─rhel_vbox-swap 253:1 0 2G 0 lvm [SWAP]
sdb 8:16 0 20G 0 disk
├─sdb1 8:17 0 10G 0 part /data
└─sdb2 8:18 0 9G 0 part
└─vg_app-lv_app 253:2 0 5G 0 lvm /App
sr0 11:0 1 51M 0 rom
At this point, /App is live, mounted and writable. A fully functioning logical volume was created.
The Real Power of LVM: Live Resizing
Here is where LVM earns its keep. Suppose /App starts running low on space. With a traditional partition, growing it is painful. With LVM, it is two commands and no downtime.

Step 1 — Review current VG and LV state
[root@rhel-node-1 rishi]# vgdisplay
--- Volume group ---
VG Name vg_app
System ID
Format lvm2
Metadata Areas 1
Metadata Sequence No 2
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 1
Open LV 1
Max PV 0
Cur PV 1
Act PV 1
VG Size <9.00 GiB
PE Size 4.00 MiB
Total PE 2303
Alloc PE / Size 1280 / 5.00 GiB
Free PE / Size 1023 / <4.00 GiB
VG UUID M88jKT-AdQv-mpoc-DBrk-ob6y-bsgf-fjiH8y
--- Volume group ---
VG Name rhel_vbox
System ID
Format lvm2
Metadata Areas 1
Metadata Sequence No 3
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 2
Open LV 2
Max PV 0
Cur PV 1
Act PV 1
VG Size <19.00 GiB
PE Size 4.00 MiB
Total PE 4863
Alloc PE / Size 4863 / <19.00 GiB
Free PE / Size 0 / 0
VG UUID 80cSNp-QG2I-mefq-t5Nk-1K7d-8S81-ucnZ3q
Showed vg_apptotal size <9.00 GiB, with 5 GiB allocated to lv_app and <4.00 GiB free (Free PE / Size) remaining in the pool. This free space is what allows extension.
[root@rhel-node-1 rishi]# lvdisplay
--- Logical volume ---
LV Path /dev/vg_app/lv_app
LV Name lv_app
VG Name vg_app
LV UUID biQSVP-z52f-KQcv-Nj35-gQUA-XSJy-dP8Nf6
LV Write Access read/write
LV Creation host, time rhel-node-1, 2026-07-13 14:30:58 +0530
LV Status available
# open 1
LV Size 5.00 GiB
Current LE 1280
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:2
--- Logical volume ---
LV Path /dev/rhel_vbox/swap
LV Name swap
VG Name rhel_vbox
LV UUID SjDoO9-vD9x-LkTW-e2Xu-Yvyq-hCla-hDs3vn
LV Write Access read/write
LV Creation host, time vbox, 2026-05-18 16:07:06 +0530
LV Status available
# open 2
LV Size 2.00 GiB
Current LE 512
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:1
--- Logical volume ---
LV Path /dev/rhel_vbox/root
LV Name root
VG Name rhel_vbox
LV UUID dntZxM-7Ncp-cu8q-3mzr-R7D2-86S2-iV7v6i
LV Write Access read/write
LV Creation host, time vbox, 2026-05-18 16:07:07 +0530
LV Status available
# open 1
LV Size <17.00 GiB
Current LE 4351
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:0
Reconfirmed lv_app was 5 GiB, in use (# open 1, since it is mounted).
Step 2 — Extend the Logical Volume
lvextend -L +3G /dev/vg_app/lv_app
Grew lv_app from 5 GB to 8 GB by adding 3 GB from the VG's free space. The + prefix means "add this much" space.
⚠️Note: this only resizes the logical volume container, the filesystem inside it does not automatically know about the extra space yet.
Check filesystem size (still old size)
[root@rhel-node-1 rishi]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 2.7G 0 2.7G 0% /dev
tmpfs 2.7G 0 2.7G 0% /dev/shm
tmpfs 1.1G 18M 1.1G 2% /run
/dev/mapper/rhel_vbox-root 17G 12G 5.1G 71% /
/dev/sda1 1014M 261M 754M 26% /boot
/dev/sdb1 9.8G 24K 9.3G 1% /data
tmpfs 542M 52K 542M 1% /run/user/42
tmpfs 542M 36K 542M 1% /run/user/1000
/dev/mapper/vg_app-lv_app 4.9G 24K 4.6G 1% /App
At this point /App still showed 4.9G total, confirming the filesystem does not catch up to the LV's new 8 GiB size yet.
Step 3 — Resize the filesystem to match
[root@rhel-node-1 rishi]# resize2fs /dev/vg_app/lv_app
resize2fs 1.46.5 (30-Dec-2021)
Filesystem at /dev/vg_app/lv_app is mounted on /App; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 1
The filesystem on /dev/vg_app/lv_app is now 2097152 (4k) blocks long.
This grows the ext4 filesystem to fill the newly available space in the LV. Since ext4 supports online resizing, this was done while the volume was mounted and in use, no unmounting needed. Output confirmed the filesystem is now resized.
Let’s verify the final size:
[root@rhel-node-1 rishi]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 2.7G 0 2.7G 0% /dev
tmpfs 2.7G 0 2.7G 0% /dev/shm
tmpfs 1.1G 18M 1.1G 2% /run
/dev/mapper/rhel_vbox-root 17G 12G 5.1G 71% /
/dev/sda1 1014M 261M 754M 26% /boot
/dev/sdb1 9.8G 24K 9.3G 1% /data
tmpfs 542M 52K 542M 1% /run/user/42
tmpfs 542M 36K 542M 1% /run/user/1000
/dev/mapper/vg_app-lv_app 7.9G 23M 7.4G 1% /App
Confirmed /App now shows 7.9G total. The extension is complete and usable.
Conclusion
LVM turns rigid, fixed-size partitions into a flexible pool you can reshape as your needs change. The day-to-day operations like extending a volume become second nature, and genuinely save you from painful repartitioning down the line.
LVM Good fit for:
- Servers with unpredictable or growing storage needs
- Environments that benefit from live resizing without downtime
- Setups that want easy snapshotting for backups
LVM Less ideal for:
- /boot partitions - most bootloaders cannot read LVM metadata directly
- Very simple single-disk setups where the flexibility is not needed
I hope this walkthrough gives you a practical starting point for working with LVM. If this tutorial helped you, feel free to clap and leave a comment, share it with your friends, and follow along for for more Linux and DevOps content.
메타데이터
- post_id
- 73ce7a9b2f38
- slug
- logical-volume-management-in-linux-rhel-9-73ce7a9b2f38
- url
- https://medium.com/@raveeshe.abeysinghe/logical-volume-management-in-linux-rhel-9-73ce7a9b2f38
- canonical_url
- https://medium.com/@raveeshe.abeysinghe/logical-volume-management-in-linux-rhel-9-73ce7a9b2f38
- author_url
- https://medium.com/@raveeshe.abeysinghe
- status
- ok
- fetched_at
- 2026-08-04 21:03:15