This is the multi-page printable view of this section. .
ZFS Storage
pg zfs turns a Linux host into a ZFS storage server: install the packages, inspect
disks, create redundancy-aware pools, and manage datasets and snapshots. It talks
directly to the kernel — no state is written to pg.yaml, and it does not need a
PostgreSQL instance.
The command family is deliberately resource-oriented: pg zfs pool create,
pg zfs dataset create, pg zfs snapshot rollback. Anything not wrapped is
reachable through pg zfs raw.
Supported systems
pg zfs install detects the distribution from /etc/os-release and installs
everything needed, including package repositories:
| Distribution | ZFS source | Version tested |
|---|---|---|
| Debian 12 (bookworm) | Debian contrib, DKMS |
2.1.11 |
| Debian 13 (trixie) | Debian contrib, DKMS |
2.3.9 |
| Ubuntu 24.04 (noble) | Ubuntu universe |
2.2.2 from the Ubuntu kernel package |
| Ubuntu 26.04 | Ubuntu universe |
2.4.1 |
| Fedora 44 | OpenZFS release RPM, DKMS | 2.4.4 |
| Rocky Linux 10 | OpenZFS release RPM + EPEL, DKMS | 2.2.11 |
These are the versions the upstream repositories shipped when the installer was
tested; the repositories decide which release you get. pg zfs status always
prints the installed userland and kernel-module versions.
Other distributions are detected and given manual instructions rather than installed automatically. Only Linux is supported.
Install
The command is zero-configuration and idempotent:
- Verifies privileges (root, or working
sudo -n). - Enables the required repository (
contribon Debian,universeon Ubuntu, the OpenZFS release repository on Fedora and Rocky). - Installs ZFS, kernel headers/module and the ZED daemon.
- Loads the module and enables the ZFS systemd units for boot.
- Prints userland and kernel-module versions.
Running it again on a healthy host is a no-op. There are no --version,
--backports or --force switches — use the distribution package manager when
you need a specific ZFS build, then let pg zfs manage it.
Verify the result at any time:
RAID levels and ZFS
ZFS has no standard RAID2/RAID3 levels. The pool types map as follows:
--type |
ZFS layout | Redundancy | Analogy |
|---|---|---|---|
stripe (raid0) |
striped vdev | none | RAID0 |
mirror (raid1) |
mirror | 1+ copies | RAID1 |
raidz1 |
single parity | 1 disk | RAID5 |
raidz2 (raid2) |
double parity | 2 disks | RAID6 |
raidz3 (raid3) |
triple parity | 3 disks | triple-parity RAID |
The aliases raid0/raid1/raid2/raid3 are accepted for convenience.
Quick start
The pool is mounted at /tank, the dataset at /tank/backups. Rollback
discards every change made after the snapshot and asks for confirmation.
Reusing disks from an old pool
ZFS keeps its vdev partitions and labels on the disks, and export does not
remove them, so the safety check refuses such disks. Destroy the old pool and
clear the leftovers before creating a new one:
If labelclear fails on the whole disk, clear the member partitions instead
(/dev/vdd1, /dev/vde1). Skip step 1 when the old pool was already destroyed.
Disk safety
pg zfs disk list reports every whole disk with its mountpoints, current ZFS
membership and whether pgcli considers it safe to hand to ZFS.
Pool creation refuses:
- the root disk, or a disk with a mounted partition;
- partitions, loop, ROM and CD-ROM devices;
- disks carrying a filesystem (
ext4,xfs, swap, LVM, LUKS, mdraid, …); - disks with an existing ZFS label;
- read-only disks;
- duplicate devices, including duplicates reached through different paths;
- disks already used by another pool.
--force skips only the confirmation prompt. It is never passed to ZFS as
-f, and it does not bypass any safety check.
Creating pools
Automatic layouts
Positional devices select the layout:
| Disks | Layout |
|---|---|
| 1 | stripe (warning: no redundancy) |
| 2 | mirror |
| 3 | raidz1 |
| 4 or more | raidz2 |
Override with --type:
Use disks of the same capacity (and similar performance) within a vdev. ZFS does not reject mixed sizes, but a vdev’s usable capacity is limited by its smallest member, so the extra space on larger disks is wasted. Different vdevs in one pool may have different capacities.
Multiple vdevs
Use repeatable --data groups together with an explicit --type. Each group is
one vdev; the pool stripes across them:
--type applies inside each --data group, not to the pool as a whole: every
group becomes one vdev of that layout, and the pool stripes across the vdevs.
All groups share the same --type; pg zfs cannot mix, for example, one mirror
vdev and one raidz2 vdev in a single create. Use pg zfs raw zpool create for a
mixed topology.
Capacity is the sum of the vdev capacities. Redundancy is per vdev: losing more disks than one vdev can tolerate takes down the whole pool.
SLOG, special, cache and spares
--special follows --type (a mirror here). --log with two or more devices is
a mirror; a single device is a stripe and is warned about. --cache and
--spare take independent device lists.
A device may appear only once across data, special, log, cache and spare.
Add a spare to an existing pool with pool add:
Default properties
Unless --no-defaults is given:
| Object | Property | Value |
|---|---|---|
| pool | ashift |
12 |
| pool | autoexpand |
on |
| pool | autotrim |
on when every data disk is non-rotational and reports discard support |
| root dataset | compression |
zstd |
| root dataset | atime |
off |
| root dataset | xattr |
sa |
| root dataset | acltype |
posixacl |
| root dataset | dnodesize |
auto |
Override pool properties with --prop/-p (maps to zpool -o) and root dataset
properties with --fs-prop/-P (maps to zpool -O):
-o is pgcli’s global output flag, which is why the pool/dataset properties use
-p/-P. Inside pg zfs raw the native flags work normally.
Managing pools
Growth and repair:
attach targets mirror vdevs. Adding disks to a raidz vdev (raidz expansion)
requires OpenZFS 2.3 or newer; Debian 12 (2.1) and Ubuntu 24.04 (2.2) cannot do
it. Use pg zfs raw zpool attach on a recent release if you need it.
Unmounting and destroying a pool
pg zfs pool export unmounts every dataset and detaches the pool from the
host, but keeps all data on the member disks:
An exported pool is not imported at boot. Attach it again by listing the importable pools and importing it:
pg zfs pool destroy deletes the pool, every dataset and all data:
destroy only works on an imported pool. If the pool was exported, import it
first:
Without --force both commands ask for confirmation; EOF and the default
answer mean “no”. Non-interactive automation must pass --force. --force
only skips the prompt — it is never passed to ZFS as -f and does not bypass
safety checks. Export is reversible; destroy is not.
Persistence and boot
Pools and datasets survive reboots without any extra steps:
pg zfs installenableszfs-load-module,zfs-import-cache,zfs-mountandzfs-zed(when the unit exists).zpool createrecords the pool in/etc/zfs/zpool.cache; on bootzfs-import-cacheimports it.- ZFS does not use
/etc/fstab. Each dataset’smountpointis a ZFS property, andzfs-mountmounts them at boot. A pool’s root dataset mounts at/<pool>, and child datasets mount under it (for example/fast/data). - The pool state lives on the member disks, so data survives a crash or power loss even if the host did not shut down cleanly.
Verify the setup:
After a reboot pg zfs pool list should list the pool and findmnt /fast
should show it mounted.
On real hardware, create pools from stable paths such as /dev/disk/by-id/....
/dev/sdX names can change across boots, and a pool whose devices are missing
will not import. Run pg zfs pool export tank before moving disks to another
host; the destination can then adopt them with pg zfs pool import tank.
Datasets and snapshots
A dataset with children requires --recursive to destroy. Rollback discards all
changes made after the snapshot.
Rollback
pg zfs snapshot rollback tank/data@snap1 returns the dataset to the state
captured by the snapshot; every change made after it is discarded. If newer
snapshots exist, ZFS refuses the rollback unless --recursive is given, which
deletes those newer snapshots. Clones that depend on the snapshot also block the
rollback — remove them first, or use pg zfs raw zfs rollback -R (which
destroys the dependent clones). Non-interactive runs need --force to skip the
confirmation prompt.
Clone
pg zfs dataset clone tank/data@snap1 tank/clone creates a new, writable
dataset from the snapshot. Unchanged blocks are shared copy-on-write, so the
clone does not duplicate the full data. Pass --prop to set properties on the
new dataset:
While a clone depends on a snapshot, the snapshot cannot be destroyed. Promote
the clone (pg zfs raw zfs promote tank/clone) to make it independent of the
original dataset before removing the old snapshot.
Raw passthrough
raw bypasses every safety check and confirmation. It is the escape hatch for
options pgcli does not wrap — treat it exactly like running zpool/zfs as
root. Binary pipelines work too, for example:
Permissions
Every command that invokes zpool or zfs first checks privileges:
-
root: runs directly;
-
non-root with working
sudo -n: all ZFS commands run throughsudo -n; -
otherwise: fails immediately with
pgcli never prompts for a sudo password. pg zfs status and pg zfs disk list
still show static information without privileges, marking pool details as
unavailable.
Command reference
| Command | Purpose |
|---|---|
pg zfs install |
Install ZFS (zero-argument, idempotent) |
pg zfs status |
Distribution, package, module and pool overview |
pg zfs disk list [--all] |
Whole disks, mounts, ZFS membership, safety |
pg zfs pool create NAME DEVICE... |
Create a pool |
pg zfs pool list |
List pools |
pg zfs pool status [NAME] [--brief] |
Detailed status |
pg zfs pool add NAME DEVICE... |
Add vdevs |
pg zfs pool attach/detach NAME DEVICE [NEW] |
Grow/shrink a mirror |
pg zfs pool replace NAME OLD NEW |
Replace a disk |
pg zfs pool offline/online NAME DEVICE |
Take a disk out/return it |
pg zfs pool export/import [NAME] |
Move a pool between systems |
pg zfs pool scrub NAME [--stop] |
Verify data integrity |
pg zfs pool destroy NAME |
Destroy a pool and all data |
pg zfs dataset create/list/set/clone/destroy |
Dataset lifecycle |
pg zfs snapshot create/list/rollback/destroy |
Snapshot lifecycle |
pg zfs raw zpool/zfs ARGS... |
Unwrapped passthrough |
Troubleshooting
pg zfs install cannot find packages. Check that the archive repository is
reachable and that contrib/universe is enabled. The installer edits only
distribution archive sources (never third-party repositories) and leaves a
*.pgcli.bak next to each modified file.
DKMS build fails. Confirm headers for the running kernel are installed
(linux-headers-$(uname -r) on Debian/Ubuntu, kernel-devel-$(uname -r) on
Fedora/Rocky). The installer reports the build log location; it never reboots or
changes the kernel. On very small VMs, add swap before installing.
Secure Boot is enabled. DKMS modules must be signed. pgcli reports the condition but does not enroll keys; sign the module with your MOK key or disable Secure Boot for the test host.
Module is not loaded. modprobe zfs then pg zfs status. The installer
enables zfs-load-module, zfs-import-cache, zfs-mount and zfs-zed so pools
import automatically on boot.
A disk is rejected because it has partitions or an old label. If it really is free, clear the leftovers:
On OpenZFS 2.2 and newer, clearing the whole disk can fail with
failed to clear label for /dev/vdb when the label lives in the ZFS member
partition. In that case clear the partition instead, then wipe the table:
Never do this to a disk that belongs to an imported pool — pg zfs disk list
shows the owning pool, and the safety check refuses such disks.
SELinux (Fedora/Rocky). ZFS mountpoints use standard file contexts. If a
service cannot access a dataset, check ausearch/journalctl for AVC denials
instead of disabling SELinux globally.
Pools do not come back after reboot. systemctl status zfs-import-cache
and zpool import as root. Pools created from /dev/sdX names follow device
renames; use stable paths (/dev/disk/by-id/...) when creating pools on real
hardware.