The following example works with an image file so that it can be tried anywhere. With a block device (/dev/vg0/data) only the value of --source changes.
3.1 Creating test data#
dd if=/dev/urandom of=data.img bs=1M count=16
dd if=/dev/zero of=data.img bs=1M seek=4 count=4 conv=notrunc # 4 MiB of zeros in the middle
mkdir backup
3.2 First backup#
bkp_agent --source data.img --out backup --name data --verify
Output:
[bkp_agent] INFO source is not an LVM logical volume: backing it up directly (no snapshot)
[bkp_agent] INFO created the store backup/default.cbs (id hash blake3, encryption none, chunks 65536/262144/1048576,
segments of 1073741824 bytes)
[bkp_agent] INFO backing up data.img (16777216 bytes) into backup/default.cbs: id hash blake3, 2 worker(s)
[bkp_agent] INFO verify: the snapshot reads back completely from backup/default.cbs
[bkp_agent] INFO done: full run 20261015T121825Z-6aa5d6f2-8419-482a-ab3f-0373e33b449d in backup/default.cbs:
source 16777216 bytes, data 13631488, holes 3145728 (fs-free 0); chunks 44, new 44 (0.0% deduplicated);
12587566 bytes sent, 12587566 newly stored
The first backup creates the store backup/default.cbs (another name with --store). This is how to read the summary: of 16 MiB of source, 13 MiB are data and 3 MiB are a hole (zeros, only recorded). The data made 44 chunks, all new. 12 MiB were stored because random data does not compress; with real data the value is smaller. --verify reads the whole backup back from the store after it is done and checks every chunk against its ID.
Note: That only 3 MiB of the 4 MiB zero area count as a hole is not an error: the rest is inside the chunks to its right and left, whose boundaries follow the content.
3.3 More backups#
# ... 1 MiB of the data changes ...
bkp_agent --source data.img --out backup --name data
Output:
[bkp_agent] INFO done: incremental run 20261015T121831Z-8c47f436-cdc7-4638-a3f8-d347e0c5ba7c in backup/default.cbs:
source 16777216 bytes, data 13631488, holes 3145728 (fs-free 0); chunks 44, new 8 (81.8% deduplicated);
2262373 bytes sent, 2262373 newly stored
Every further backup is the same command. The agent reads the whole source, but recognizes 36 of the 44 chunks and stores only the 8 new ones (2.2 MiB). Still, the new run is a complete state: it can be restored without the first one, and the first one can be deleted without damaging the second. An option like --type is not needed (default inc); --type full starts a new, independent generation (Chapter 6).
3.4 What is in the store#
bkp_restore --store backup/default.cbs --list
Output:
RUN CREATED NAME TYPE SOURCE DATA SENT NEW STATUS
20261015T121825Z-6aa5d6f2-8419-482a-ab3f-0373e33b449d 2026-10-15 14:18:25 data Full 16.0 MiB 13.0 MiB 12.0 MiB 12.0 MiB ok
20261015T121831Z-8c47f436-cdc7-4638-a3f8-d347e0c5ba7c 2026-10-15 14:18:31 data Incremental 16.0 MiB 13.0 MiB 2.2 MiB 2.2 MiB ok
every run restores on its own (full and incremental alike), a VM run one disk at a time (--disk)
For every run the list shows: point in time (local time), backup name, Full/Incremental, size of the source, data (without holes), bytes sent and newly stored. On disk the store looks like this:
Layout of a store
backup/default.cbs/
store.json ID hash, encryption, chunk sizes (fixed when created)
packs/<xx>/<id>.pack chunks and management objects, about 64 MiB each
runs/<time>-<uuid>.run one small file per backup (commit point)
index.lmdb index (cache, rebuilt from the packs)
Caution: A store is one unit. Never copy or delete single files in it by hand; for copies there is bkp_maintenance mirror, for single runs export, and for deleting prune or delete (Chapter 10).
3.5 Restoring#
bkp_restore --store backup/default.cbs --name data --target restored.img
cmp data.img restored.img && echo IDENTICAL
Output:
[bkp_restore] INFO restoring run 20261015T121831Z-8c47f436-cdc7-4638-a3f8-d347e0c5ba7c ('data' of 2026-10-15 14:18:31,
16777216 bytes) from backup/default.cbs to restored.img
[bkp_restore] INFO done: 16777216 bytes restored (data 13631488, holes 3145728), 44 chunks, 12583143 stored bytes read
IDENTICAL
--name takes the newest run of this name. An older state is chosen with --at "2026-10-15 14:20" (the newest run up to this point in time) or --run <run> with the name from --list. Every chunk is checked against its ID while it is read; the restore needs no option of its own for checking (Chapter 7).
3.6 Important options of the agent#
| Option | Meaning |
|---|---|
--store <name> | Name of the store (default default), kept in <out>/<name>.cbs. Similar machines sensibly share one store |
--type inc|full | inc (default): normal run. full: starts a new store generation (Chapter 6). diff no longer exists |
--store-generation none|<n>d|<n>m | automatically a new generation when the current one is older (default none) |
--chunk-size <avg> | average chunk size of a new store or a new generation (power of two, default 256K; Chapter 25) |
--hash sha256|blake3|xxh3-128 | ID hash of a new store (see below) |
--threads <n>, --chunk-threads <n> | workers (hash and compression) and 1 GiB segments chunked at the same time |
--compression zstd|lz4|none, --level <n> | compression of every new chunk; default zstd, level 3 |
--max-level <n> | adaptive raising of the zstd level up to at most n (max. 22): the level goes up as long as the writer is the bottleneck and the workers have room |
--verify | reads the whole backup back from the store after it is done; local stores only, skipped with a server |
--no-fs-bitmap | read and back up free areas too: the restore is then bit-identical to the device |
--retention <spec>, --retention-class <class> | retention of this backup (7d, 5w, 12m, 1y, forever or GFS such as gfs:7d,4w,12m,5y), kept in the run (Chapter 10) |
--index-dir <dir> | keep the index of the store elsewhere (e.g. on an SSD); required when the store is on NFS/SMB |
--background | throttles the backup itself when the machine is busy |
--no-progress | report no progress (no run channel, Chapter 13); such a run can then not be paused or throttled either |
Caution: Free areas on ext2/3/4 and NTFS are not backed up and are filled with zeros on restore. The restored file system is identical (fsck clean, all files the same), but the raw data is not: a SHA-256 over the whole device differs. For a bit-identical copy use --no-fs-bitmap.
Note: Options from version 1 (--parent, --block-size, --search-dir, --type diff, --resume verify|trust) are rejected with a hint to their replacement, for example: --type diff no longer exists: every backup can be restored on its own; for an independent base use --type full.
3.7 Choosing the ID hash (--hash)#
Every chunk is recognized by its content hash, its ID. The same ID means the same content: deduplication rests on this, and every read (restore, --verify, scrub) checks the content against the ID. The method applies to a whole store and is fixed when the store is created (id_hash in store.json):
--hash | Properties |
|---|---|
sha256 | cryptographic; with SHA-NI about 2 GB/s per core, without it much less, and then often the CPU bottleneck |
blake3 (default) | cryptographic (256 bits), uses SSE4.1/AVX2/AVX-512; on modern CPUs hardly slower than xxh3 and many times faster than SHA-256 without SHA-NI |
xxh3-128 | not cryptographic (128 bits), the fastest. Random collisions are practically impossible, deliberately made ones are not |
Tip: Which one? sha256 and blake3 are safe even when agents that do not trust each other share a store: nobody can build a chunk that has the ID of someone else's chunk. xxh3-128 is meant only for stores that only trustworthy agents write into. Otherwise whoever controls the content of a source could slip in a chunk with the same ID, which would then end up in other backups in place of the real one.
The default blake3 is usually the best choice for new stores. Without --hash a run creates a new store with blake3 and otherwise takes the method of the existing store; stores from before 2.0.0-alpha.017 keep sha256.
bkp_agent --source data.img --out backup --store fast --name data --hash blake3
bkp_agent --source data.img --out backup --store fast --name data # takes blake3 from the store
Without --hash a run takes the method of the existing store; an explicitly given --hash that differs is rejected (like a wrong key). The way to change it is a new generation: --type full --hash blake3 (Chapter 6). In encrypted stores the ID is formed with a key derived from the store key; equal IDs then reveal nothing about the content without the key. In the configuration files the key is called hash.