Disks and filesystems: add, mount, grow and repair
How disks, partitions, filesystems and mount points fit together, how to add a new disk and mount it permanently with fstab, grow storage with LVM, add swap, and check a filesystem safely.
By the end of this lesson you'll be able to
0 of 6 completeThe storage stack
Linux storage is layers. Once you can name the layer you're looking at, every command makes sense:
$ lsblk # disks and partitions, as a tree NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS nvme0n1 259:0 0 40G 0 disk ├─nvme0n1p1 259:1 0 39.9G 0 part / └─nvme0n1p15 259:2 0 106M 0 part /boot/efi nvme1n1 259:3 0 50G 0 disk $ lsblk -f # plus filesystem type and UUID $ df -Th # mounted filesystems: type, size, use $ findmnt / # how one mount point is mounted, with options TARGET SOURCE FSTYPE OPTIONS / /dev/nvme0n1p1 ext4 rw,relatime,discard,errors=remount-ro
df -Thdfdisk space per filesystem
Here nvme1n1 is a new, empty 50 GB disk: no partitions, nothing mounted. Disk names are
sda, sdb on many machines, vda on some VMs and nvme0n1 on newer hardware and cloud
instances.
Filesystems and inodes
A filesystem organises a partition into files and folders. The two you'll meet on servers:
| ext4 | XFS | |
|---|---|---|
| Default on | Ubuntu, Debian | RHEL, Rocky, Amazon Linux |
| Grow while mounted | yes | yes |
| Shrink | yes (unmounted) | no |
| Check / repair | e2fsck (fsck) | xfs_repair |
| Tune | tune2fs | xfs_admin |
Every file has an inode: a record of its owner, permissions, size and where its data is. Its
name lives in the folder; the inode holds the rest (you saw inode numbers with ls -i in the
Working with files lesson). On ext4 the number of inodes is fixed when the filesystem is made:
$ df -i / Filesystem Inodes IUsed IFree IUse% Mounted on /dev/nvme0n1p1 2621440 2621440 0 100% /
df -i /dfdisk space per filesystem
That's a disk that is "full" with gigabytes free: millions of tiny files (sessions, cache, mail) used every inode. Find the folders holding the most entries:
$ sudo du --inodes -x / 2>/dev/null | sort -n | tail -5 # inode use per folder, biggest last
sudo du --inodes -x / 2>/dev/null | sort -n | tail -5sudorun the next command as rootdudisk space used by files|pipe: send output to the next commandsortsort lines-nsort as numberstailshow the last lines
Adding a new disk
$ sudo parted /dev/nvme1n1 --script mklabel gpt mkpart data ext4 0% 100% # one partition, whole disk $ lsblk /dev/nvme1n1 NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS nvme1n1 259:3 0 50G 0 disk └─nvme1n1p1 259:4 0 50G 0 part $ sudo mkfs.ext4 -L data /dev/nvme1n1p1 # create the filesystem (-L sets a label) $ sudo mkdir -p /data $ sudo mount /dev/nvme1n1p1 /data # mount it now $ df -h /data
sudo parted /dev/nvme1n1 --script mklabel gpt mkpart data ext4 0% 100%sudorun the next command as root
sudo mkdir -p /datamkdirmake a directory-pcreate missing parents, no error if it exists
df -h /datadfdisk space per filesystem-hhuman-readable sizes
fdisk (MBR or GPT) and gdisk (GPT) do the same job interactively. Use GPT for new disks:
MBR can't handle disks over 2 TB.
Mounting: now and at every boot
mount lasts until the next reboot. To mount at every boot, add a line to /etc/fstab, using
the filesystem's UUID (device names can change when disks are added):
$ sudo blkid /dev/nvme1n1p1 /dev/nvme1n1p1: LABEL="data" UUID="3f9c2a1e-8b7d-4c1a-9e2f-6a5b4c3d2e1f" BLOCK_SIZE="4096" TYPE="ext4"
sudo blkid /dev/nvme1n1p1sudorun the next command as root
# /etc/fstab # <what> <where> <type> <options> <dump> <fsck order> UUID=3f9c2a1e-8b7d-4c1a-9e2f-6a5b4c3d2e1f /data ext4 defaults,nofail 0 2
| Field | Means |
|---|---|
| what | UUID=… (or LABEL=data) |
| where | the mount point folder |
| type | ext4, xfs, swap… |
| options | defaults; nofail boots even if this disk is missing; noatime saves writes |
| dump | 0 (nobody uses dump any more) |
| fsck order | 1 for /, 2 for other filesystems, 0 never check |
$ sudo umount /data $ sudo mount -a # mount everything in fstab now: errors show here, not at boot $ sudo findmnt --verify # check fstab for mistakes $ findmnt /data
sudo umount /datasudorun the next command as root
umount fails with "target is busy" while something uses the mount: find what with
sudo lsof +D /data or sudo fuser -vm /data, and stop it first.
LVM: storage you can grow
With plain partitions, a full disk means moving data. LVM (Logical Volume Manager) adds a flexible layer: disks become physical volumes (PV), pooled into a volume group (VG), from which you cut logical volumes (LV). Need more space? Add a disk to the group and grow the volume, while it's mounted.
$ sudo pvcreate /dev/nvme1n1 # 1. make the disk a physical volume $ sudo vgcreate data /dev/nvme1n1 # 2. a volume group called data $ sudo lvcreate -n app -L 20G data # 3. a 20 GB logical volume: /dev/data/app $ sudo mkfs.ext4 /dev/data/app && sudo mount /dev/data/app /srv/app $ sudo pvs && sudo vgs && sudo lvs # what you have, layer by layer $ sudo lvextend -r -L +10G /dev/data/app # later: grow by 10 GB, filesystem included $ sudo vgextend data /dev/nvme2n1 # out of room? add another disk to the group
sudo pvcreate /dev/nvme1n1sudorun the next command as root
sudo mkfs.ext4 /dev/data/app && sudo mount /dev/data/app /srv/app&&run the next command only if this one worked
-r on lvextend resizes the filesystem in the same step; without it, the volume grows but the
filesystem doesn't, and df shows no change. Cloud disks often grow without LVM too: resize the
disk in the console, then sudo growpart /dev/nvme0n1 1 and sudo resize2fs /dev/nvme0n1p1
(xfs_growfs / for XFS).
Swap
Swap is disk space used as overflow memory. It's slow, but it lets a server survive a memory spike instead of having the kernel kill a process. A small swap file is enough for most servers:
$ sudo fallocate -l 2G /swapfile $ sudo chmod 600 /swapfile # only root may read it: it holds memory contents $ sudo mkswap /swapfile && sudo swapon /swapfile $ swapon --show NAME TYPE SIZE USED PRIO /swapfile file 2G 0B -2 $ echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab # keep it after reboot
sudo fallocate -l 2G /swapfilesudorun the next command as root
sudo chmod 600 /swapfilechmodchange permissions
sudo mkswap /swapfile && sudo swapon /swapfile&&run the next command only if this one worked
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstabechoprint text|pipe: send output to the next commandteewrite input to a file and pass it on-aappend
Constant swapping (free -h showing swap use growing, high %wa in top) means the server needs
more RAM or a lighter workload, not more swap.
Checking and repairing
$ sudo umount /data # never check a mounted filesystem $ sudo e2fsck -f /dev/nvme1n1p1 # ext4 (fsck picks the right tool too) $ sudo xfs_repair /dev/data/app # XFS $ sudo tune2fs -l /dev/nvme1n1p1 | grep -i "mount count\|state" # ext4 health info
sudo umount /datasudorun the next command as root
sudo tune2fs -l /dev/nvme1n1p1 | grep -i "mount count\|state"|pipe: send output to the next commandgrepfind lines that match a pattern-iignore upper/lower case
Filesystems mounted at boot (/) are checked automatically when needed. For a damaged root
filesystem you boot from a rescue image, or on a cloud VM attach the disk to another instance and
check it there. dmesg and journalctl -k show disk errors (I/O error, EXT4-fs error): on
physical hardware, that's often the disk itself failing, and the fix is a backup and a new disk.
Add a data disk the safe way
A new empty disk is attached to the server for the app's uploads. Create a filesystem on it, mount it at /data so it survives reboots, and prove the fstab line works before any reboot. Practise on a file-backed disk so nothing real is at risk.
/data mounted from /etc/fstabReveal one safe solution
$ truncate -s 1G ~/disk.img # a fake 1 GB disk, safe to practise on $ LOOP=$(sudo losetup -f --show ~/disk.img) && echo $LOOP /dev/loop0 $ sudo mkfs.ext4 -q -L data $LOOP $ UUID=$(sudo blkid -s UUID -o value $LOOP) && echo $UUID 3f9c2a1e-8b7d-4c1a-9e2f-6a5b4c3d2e1f $ sudo mkdir -p /data $ echo "UUID=$UUID /data ext4 defaults,nofail 0 2" | sudo tee -a /etc/fstab $ sudo mount -a && sudo findmnt --verify && findmnt /data TARGET SOURCE FSTYPE OPTIONS /data /dev/loop0 ext4 rw,relatime $ echo "uploads work" | sudo tee /data/test.txt
LOOP=$(sudo losetup -f --show ~/disk.img) && echo $LOOP&&run the next command only if this one workedechoprint text
sudo mkfs.ext4 -q -L data $LOOPsudorun the next command as root
sudo mkdir -p /datamkdirmake a directory-pcreate missing parents, no error if it exists
echo "UUID=$UUID /data ext4 defaults,nofail 0 2" | sudo tee -a /etc/fstab|pipe: send output to the next commandteewrite input to a file and pass it on-aappend
mount -a mounting /data from the fstab line is your proof it will work at boot. On a real disk,
skip truncate and losetup and use its name from lsblk instead. To clean up the practice:
sudo umount /data, remove the line from /etc/fstab, and sudo losetup -d /dev/loop0.
Try it yourself
0 of 6 steps doneRun each task in your own account or terminal, then tick it off.
Quick check
Why put UUID=… instead of /dev/sdb1 in /etc/fstab?
Official reading for this lesson
Or get every quiz answer right and it completes itself.