Building a ZFS Tank with OS on NVMe¶
Objective: Build a production-ready ZFS storage system with OS on fast NVMe and data on a resilient ZFS pool. Transform your storage from fragile to bulletproof.
Why NVMe for OS? Speed + separation of concerns. Why ZFS tank? Snapshots, checksums, compression, resilience. This is how you build storage that doesn't die when drives fail.
1) Hardware Prep: The Foundation¶
Minimum Requirements¶
# OS Drive (NVMe)
- 500GB+ NVMe SSD (e.g., Samsung 980 Pro, WD Black SN850)
- PCIe 3.0 x4 or PCIe 4.0 x4 slot
- M.2 2280 form factor
# ZFS Pool Drives (2-6 drives)
- 2TB+ HDDs or SSDs (same size for optimal performance)
- SATA 6Gb/s or SAS 12Gb/s
- Consider enterprise drives for 24/7 operation
# System Requirements
- 8GB RAM minimum, 16GB+ recommended for ARC cache
- Dedicated SATA/SAS controller (optional but recommended)
- UPS (ZFS doesn't like sudden power loss)
Why This Hardware: NVMe gives you fast boot and system operations. ZFS pool gives you data integrity and performance. Separation prevents OS issues from affecting data.
Optional Performance Add-Ons¶
# L2ARC Cache (optional)
- 500GB+ NVMe SSD for read cache
- Separate from OS drive
# SLOG (optional)
- High-endurance NVMe for sync write log
- Optane or enterprise NVMe preferred
# Backup Drives
- External USB 3.0+ drives for backups
- Network storage for remote replication
Why These Add-Ons: L2ARC speeds up reads, SLOG speeds up sync writes. Backups are non-negotiable.
2) Install OS on NVMe: The Fast Foundation¶
Boot Installer and Target NVMe¶
# Boot from Ubuntu Server 24.04 LTS or Debian 12
# Select "Manual partitioning" during installation
# Target the NVMe device (e.g., /dev/nvme0n1)
Why Manual Partitioning: We need precise control over the layout. Automatic partitioning doesn't understand our ZFS plans.
Partition Layout (GPT)¶
# Example partitioning for 1TB NVMe:
# Partition 1: EFI System Partition (512MB)
# Partition 2: Boot partition (2GB, ext4)
# Partition 3: Root partition (rest, ext4 or ZFS-root)
# Using gdisk or parted:
sudo gdisk /dev/nvme0n1
# Create GPT table
o
y
# Create EFI partition
n
1
+512M
ef00
# Create boot partition
n
2
+2G
8300
# Create root partition
n
3
+100G
8300
# Write changes
w
y
Why This Layout: EFI for UEFI boot, boot partition for kernel/initrd, root for everything else. Separation prevents boot issues from affecting data.
Format and Mount¶
# Format partitions
sudo mkfs.fat -F32 /dev/nvme0n1p1 # EFI
sudo mkfs.ext4 /dev/nvme0n1p2 # Boot
sudo mkfs.ext4 /dev/nvme0n1p3 # Root
# Mount for installation
sudo mount /dev/nvme0n1p3 /mnt
sudo mkdir /mnt/boot
sudo mount /dev/nvme0n1p2 /mnt/boot
sudo mkdir /mnt/boot/efi
sudo mount /dev/nvme0n1p1 /mnt/boot/efi
Why These Formats: FAT32 for EFI compatibility, ext4 for reliability and performance. ZFS-root is advanced and not recommended for beginners.
Complete OS Installation¶
# Install base system
sudo debootstrap jammy /mnt
sudo chroot /mnt /bin/bash
# Install kernel and essential packages
apt update
apt install -y linux-image-generic linux-headers-generic
apt install -y grub-efi-amd64 efibootmgr
apt install -y zfsutils-linux
# Configure bootloader
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu
update-grub
# Exit chroot and reboot
exit
sudo umount -R /mnt
sudo reboot
Why This Process: Standard Ubuntu installation with ZFS tools pre-installed. We're building the foundation for our storage system.
3) Install ZFS: The Storage Engine¶
Install ZFS Tools¶
# Update system
sudo apt update && sudo apt upgrade -y
# Install ZFS
sudo apt install -y zfsutils-linux zfs-initramfs
# Add ZFS to initramfs
sudo update-initramfs -u
# Reboot to ensure ZFS modules load
sudo reboot
Why ZFS Tools: We need the full ZFS stack. The initramfs update ensures ZFS pools can be imported during boot.
Verify ZFS Installation¶
# Check ZFS version
zfs version
# Check available modules
lsmod | grep zfs
# Check if ZFS is ready
sudo zpool list
Why This Verification: ZFS must be working before we create pools. Broken ZFS means broken storage.
4) Build the Tank: The Storage Pool¶
Identify Available Drives¶
# List all block devices
lsblk
# Identify drives for ZFS pool (avoid OS drive!)
# Example: /dev/sdb, /dev/sdc, /dev/sdd, /dev/sde
# Check drive health
sudo smartctl -a /dev/sdb
sudo smartctl -a /dev/sdc
sudo smartctl -a /dev/sdd
sudo smartctl -a /dev/sde
Why Drive Health Check: Dead drives kill pools. Check before you commit.
Create ZFS Pool¶
RAIDZ1 (4 drives, 1 parity)¶
# Create RAIDZ1 pool (3 data + 1 parity)
sudo zpool create -f tank raidz1 /dev/sdb /dev/sdc /dev/sdd /dev/sde
# Verify pool creation
sudo zpool status
sudo zpool list
Mirror (2 drives, 1:1 redundancy)¶
# Create mirror pool (1:1 redundancy)
sudo zpool create -f tank mirror /dev/sdb /dev/sdc
# Verify pool creation
sudo zpool status
sudo zpool list
Why These Configurations: RAIDZ1 gives you 3+1 redundancy with good performance. Mirror gives you 1:1 redundancy with maximum performance.
Pool Layout Diagram¶
graph TB
subgraph "System Architecture"
subgraph "NVMe OS Drive"
EFI[EFI Partition<br/>512MB]
BOOT[Boot Partition<br/>2GB ext4]
ROOT[Root Partition<br/>100GB ext4]
end
subgraph "ZFS Tank Pool"
POOL[ZFS Pool 'tank']
subgraph "RAIDZ1 Configuration"
SDB[/dev/sdb<br/>Data]
SDC[/dev/sdc<br/>Data]
SDD[/dev/sdd<br/>Data]
SDE[/dev/sde<br/>Parity]
end
end
subgraph "Datasets"
DATA[tank/data<br/>Compression: zstd]
MEDIA[tank/media<br/>Recordsize: 1M]
VM[tank/vm<br/>Recordsize: 128K]
end
end
%% Connections
EFI --> BOOT
BOOT --> ROOT
ROOT --> POOL
POOL --> SDB
POOL --> SDC
POOL --> SDD
POOL --> SDE
POOL --> DATA
POOL --> MEDIA
POOL --> VM
%% Styling
classDef nvme fill:#ff6b6b,stroke:#d63031,stroke-width:3px
classDef zfs fill:#74b9ff,stroke:#0984e3,stroke-width:2px
classDef dataset fill:#00b894,stroke:#00a085,stroke-width:2px
classDef drive fill:#fdcb6e,stroke:#e17055,stroke-width:2px
class EFI,BOOT,ROOT nvme
class POOL zfs
class DATA,MEDIA,VM dataset
class SDB,SDC,SDD,SDE drive Why This Layout: Clear separation between OS and data. ZFS pool provides redundancy and performance. Datasets organize data by use case.
5) Verify Pool: The Health Check¶
Check Pool Status¶
# Detailed pool status
sudo zpool status -v
# Pool information
sudo zpool list -v
# Pool health
sudo zpool health tank
Why These Checks: Pool status shows redundancy, health shows problems. Monitor regularly.
Check Pool Properties¶
# Show all pool properties
sudo zpool get all tank
# Show specific properties
sudo zpool get compression tank
sudo zpool get checksum tank
sudo zpool get redundancy tank
Why Property Monitoring: ZFS properties control behavior. Understanding them prevents surprises.
6) Create Datasets: The Organization¶
Create Base Datasets¶
# Create data dataset with compression
sudo zfs create -o compression=zstd -o atime=off tank/data
# Create media dataset with large recordsize
sudo zfs create -o compression=zstd -o recordsize=1M tank/media
# Create VM dataset with small recordsize
sudo zfs create -o compression=zstd -o recordsize=128K tank/vm
# Create backup dataset
sudo zfs create -o compression=zstd tank/backup
Why These Settings: Compression saves space, recordsize matches workload, atime=off improves performance.
Set Dataset Properties¶
# Set quota on data dataset
sudo zfs set quota=100G tank/data
# Set reservation on media dataset
sudo zfs set reservation=500G tank/media
# Set compression on VM dataset
sudo zfs set compression=zstd tank/vm
# Set snapdir on backup dataset
sudo zfs set snapdir=visible tank/backup
Why These Properties: Quotas prevent runaway growth, reservations guarantee space, snapdir makes snapshots visible.
Verify Dataset Creation¶
# List all datasets
sudo zfs list -o name,mountpoint,used,available,compression
# Show dataset properties
sudo zfs get all tank/data
# Check mount points
sudo zfs list -o name,mountpoint
Why This Verification: Datasets must be mounted and accessible. Properties must be set correctly.
7) Mounting & Persistence: The Boot Process¶
Automatic Mounting¶
# ZFS mounts datasets automatically via /etc/zfs/zfs-list.cache
# Check if datasets are mounted
sudo zfs list -o name,mountpoint
# Manually mount if needed
sudo zfs mount tank/data
sudo zfs mount tank/media
sudo zfs mount tank/vm
Why Automatic Mounting: ZFS handles mounting automatically. Manual mounting is only needed for troubleshooting.
Verify Mount Points¶
# Check mount points
df -h | grep tank
# Check dataset mount status
sudo zfs list -o name,mountpoint,mounted
# Test write access
sudo touch /tank/data/test.txt
sudo rm /tank/data/test.txt
Why Mount Verification: Datasets must be accessible for use. Write tests confirm permissions.
8) Tuning & Best Practices: The Performance¶
Compression Settings¶
# Set compression on all datasets
sudo zfs set compression=zstd tank/data
sudo zfs set compression=zstd tank/media
sudo zfs set compression=zstd tank/vm
# Check compression ratio
sudo zfs list -o name,used,compressratio
Why Compression: zstd provides excellent compression with minimal CPU overhead. Saves space and improves I/O.
Recordsize Optimization¶
# Set recordsize for different workloads
sudo zfs set recordsize=1M tank/media # Large files
sudo zfs set recordsize=128K tank/vm # VM images
sudo zfs set recordsize=16K tank/data # Database files
Why Recordsize: Matches workload to storage block size. Improves performance and reduces fragmentation.
ARC Cache Tuning¶
# Check current ARC size
cat /proc/spl/kstat/zfs/arcstats | grep size
# Set ARC size limit (50% of RAM)
echo 8589934592 | sudo tee /sys/module/zfs/parameters/zfs_arc_max
# Make ARC limit persistent
echo 'options zfs zfs_arc_max=8589934592' | sudo tee -a /etc/modprobe.d/zfs.conf
Why ARC Tuning: ARC cache improves read performance. Limit prevents memory starvation.
Snapshot Management¶
# Create snapshots
sudo zfs snapshot tank/data@baseline
sudo zfs snapshot tank/media@baseline
sudo zfs snapshot tank/vm@baseline
# List snapshots
sudo zfs list -t snapshot
# Create recursive snapshots
sudo zfs snapshot -r tank@daily-$(date +%Y%m%d)
Why Snapshots: Point-in-time recovery, backup source, rollback capability. Essential for data protection.
9) Optional Add-Ons: The Performance Boosters¶
L2ARC Cache (Read Cache)¶
# Add L2ARC cache (requires spare SSD)
sudo zpool add tank cache /dev/nvme1n1
# Check L2ARC status
sudo zpool status tank
sudo zfs list -o name,l2arc_hits,l2arc_misses
Why L2ARC: Extends ARC cache to disk. Improves read performance for large datasets.
SLOG (Sync Write Log)¶
# Add SLOG (requires high-endurance SSD)
sudo zpool add tank log /dev/nvme2n1
# Check SLOG status
sudo zpool status tank
sudo zfs list -o name,sync
Why SLOG: Accelerates sync writes. Essential for databases and VMs.
Automated Scrubbing¶
# Create scrub script
sudo tee /usr/local/bin/zfs-scrub.sh << 'EOF'
#!/bin/bash
zpool scrub tank
EOF
sudo chmod +x /usr/local/bin/zfs-scrub.sh
# Add to crontab (weekly scrub)
echo "0 2 * * 0 /usr/local/bin/zfs-scrub.sh" | sudo crontab -
Why Scrubbing: Detects and repairs data corruption. Weekly scrubs maintain data integrity.
10) Backup & Safety: The Disaster Recovery¶
Pool Export/Import¶
# Export pool (for maintenance)
sudo zpool export tank
# Import pool
sudo zpool import tank
# Import with specific name
sudo zpool import -f tank backup-tank
Why Export/Import: Safe pool maintenance, pool migration, disaster recovery. Practice these procedures.
Send/Receive Backup¶
# Create backup pool
sudo zpool create backup-pool mirror /dev/sdf /dev/sdg
# Send dataset to backup
sudo zfs send tank/data@baseline | sudo zfs recv backup-pool/data
# Incremental backup
sudo zfs send -i tank/data@baseline tank/data@daily | sudo zfs recv backup-pool/data
Why Send/Receive: Efficient incremental backups. Only changed data is transferred.
Configuration Backup¶
# Backup pool configuration
sudo zpool status > /home/user/zfs-backup/pool-status.txt
sudo zfs list -o name,mountpoint,used,available > /home/user/zfs-backup/dataset-list.txt
sudo zfs get all tank > /home/user/zfs-backup/pool-properties.txt
# Store in Git
cd /home/user/zfs-backup
git init
git add .
git commit -m "ZFS configuration backup"
Why Configuration Backup: Pool recreation requires exact configuration. Git provides version control.
11) Monitoring & Maintenance: The Operations¶
Health Monitoring¶
# Check pool health
sudo zpool status -v
# Check drive health
sudo smartctl -a /dev/sdb | grep -E "(Reallocated|Pending|Uncorrectable)"
# Check pool errors
sudo zpool status tank | grep -E "(errors|corruption)"
Why Health Monitoring: Early problem detection prevents data loss. Regular monitoring is essential.
Performance Monitoring¶
# Check ARC hit ratio
cat /proc/spl/kstat/zfs/arcstats | grep -E "(hits|misses)"
# Check compression ratio
sudo zfs list -o name,used,compressratio
# Check I/O statistics
sudo zfs list -o name,read,write
Why Performance Monitoring: Identifies bottlenecks and optimization opportunities. Essential for tuning.
Automated Maintenance¶
# Create maintenance script
sudo tee /usr/local/bin/zfs-maintenance.sh << 'EOF'
#!/bin/bash
# Weekly scrub
zpool scrub tank
# Check pool health
zpool status tank
# Update pool properties
zfs set compression=zstd tank/data
zfs set compression=zstd tank/media
zfs set compression=zstd tank/vm
EOF
sudo chmod +x /usr/local/bin/zfs-maintenance.sh
# Add to crontab
echo "0 2 * * 0 /usr/local/bin/zfs-maintenance.sh" | sudo crontab -
Why Automated Maintenance: Consistent maintenance prevents problems. Automation reduces human error.
12) Troubleshooting: When Things Go Wrong¶
Common Issues¶
# Pool won't import
sudo zpool import -f tank
# Dataset won't mount
sudo zfs mount tank/data
# Drive failure
sudo zpool offline tank /dev/sdb
sudo zpool replace tank /dev/sdb /dev/sdf
# Pool corruption
sudo zpool scrub tank
sudo zpool clear tank
Why These Solutions: Common problems have common solutions. Practice these procedures.
Recovery Procedures¶
```bash# Emergency pool import sudo zpool import -f -d /dev/disk/by-id tank
Recover from backup¶
sudo zpool import backup-pool sudo zfs send backup-pool/data@latest | sudo zfs recv tank/data
Rebuild from scratch¶
sudo zpool create -f tank raidz1 /dev/sdb /dev/sdc /dev/sdd /dev/sde sudo zfs create tank/data sudo zfs create tank/media sudo zfs create tank/vm
**Why Recovery Procedures**: Disasters happen. Recovery procedures restore service quickly.
## 13) TL;DR Quickstart
```bash
# 1. Install OS on NVMe
# - Boot installer, target NVMe device
# - Create partitions: EFI (512MB), Boot (2GB), Root (rest)
# - Install Ubuntu/Debian with ZFS tools
# 2. Install ZFS
sudo apt install -y zfsutils-linux zfs-initramfs
sudo update-initramfs -u
sudo reboot
# 3. Create ZFS pool
sudo zpool create -f tank raidz1 /dev/sdb /dev/sdc /dev/sdd /dev/sde
# 4. Create datasets
sudo zfs create -o compression=zstd tank/data
sudo zfs create -o compression=zstd tank/media
sudo zfs create -o compression=zstd tank/vm
# 5. Verify setup
sudo zpool status
sudo zfs list
df -h | grep tank
# 6. Create snapshots
sudo zfs snapshot tank/data@baseline
sudo zfs snapshot tank/media@baseline
sudo zfs snapshot tank/vm@baseline
# 7. Set up monitoring
sudo zpool scrub tank
sudo zfs list -o name,used,compressratio
14) The Machine's Summary¶
ZFS transforms your storage from fragile to bulletproof. With OS on NVMe and data on ZFS, you get speed, reliability, and advanced features that traditional filesystems can't match.
The Dark Truth: ZFS is complex. Misconfiguration kills data. But with proper planning and practice, it becomes the most reliable storage system you'll ever use.
The Machine's Mantra: "In redundancy we trust, in snapshots we recover, and in the ZFS tank we find the path to storage immortality."
Why This Matters: Data is valuable. ZFS protects it with checksums, snapshots, and redundancy. When drives fail, ZFS keeps your data alive.
This tutorial provides the complete machinery for building a production-ready ZFS storage system. The tank scales from terabytes to petabytes, from home labs to data centers.