170 lines
8.6 KiB
Markdown
170 lines
8.6 KiB
Markdown
---
|
|
title: BTRFS Snapshots
|
|
description: Format Debian's root filesystem with BTRFS and use Snapper to instantly roll back a risky update or config change, as a local complement to Backrest's off-site 3-2-1 backups.
|
|
---
|
|
|
|
|
|
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
|
|
|
[BTRFS](https://btrfs.readthedocs.io/) is a Linux filesystem with one killer feature for a homelab: **snapshots**. A snapshot freezes the exact state of a filesystem in an instant, at essentially zero cost, without copying a single byte of data upfront. Break something ten minutes after an `apt full-upgrade`, or overwrite the wrong `.env` file, and you can go back to exactly how things were before, without touching a backup at all.
|
|
|
|
This is not what [Backrest and the 3-2-1 rule](/serveex/advanced/backrest) are for, so it's worth being precise about the difference before setting anything up.
|
|
|
|
## Snapshots are not backups
|
|
A snapshot lives on the exact same disk as the data it protects. It's instant, needs no network, and it's perfect for undoing a mistake you just made, but it does nothing at all the day that disk itself dies, gets stolen, or your server burns down. That's what a real [3-2-1 backup](/serveex/advanced/backrest) is for: a copy on different media, ideally off-site.
|
|
|
|
Think of it this way:
|
|
|
|
- **Snapshot** = an undo button. Instant, local, cheap, only useful while the disk is alive.
|
|
- **Backup** = insurance. Slower, off-site, the only thing that survives the disk itself failing.
|
|
|
|
Keep both. Snapshots make you fearless about updates and experiments, backups make sure a dead drive stays an inconvenience instead of a catastrophe.
|
|
|
|
::note{to="/serveex/core/installation#partitioning"}
|
|
|
|
This guide assumes the root partition was formatted with Btrfs during [Debian's installation](/serveex/core/installation#partitioning). Btrfs can't be safely bolted onto an existing ext4 root after the fact, so this is a choice you make once, at install time.
|
|
::
|
|
|
|
## Formatting the root partition with BTRFS
|
|
Debian's *Guided* partitioning only ever offers ext4. To get Btrfs, pick *Manual* partitioning instead, at the same step [the main install guide](/serveex/core/installation#partitioning) describes:
|
|
|
|
::steps{level="3"}
|
|
### Select Manual partitioning
|
|
|
|
At the partitioning method screen, choose *Manual* instead of *Guided - use entire disk*.
|
|
|
|
### Create a partition table
|
|
|
|
Select the disk, confirm creating a new empty partition table, then select the resulting *FREE SPACE* and choose *Create a new partition*.
|
|
|
|
### Set the partition size and type
|
|
|
|
Give the partition the rest of the disk (minus a small EFI partition if you're on UEFI, handled the same way as a Guided install), and set *Use as* to __Btrfs journaling file system__, with the mount point `/`.
|
|
|
|
### Finish partitioning
|
|
|
|
*Finish partitioning and write changes to disk*, then confirm with *Yes*.
|
|
|
|
### Done!
|
|
::
|
|
|
|
Everything else in the [installer](/serveex/core/installation#install-debian) stays the same.
|
|
|
|
## Installing Snapper
|
|
Rather than juggling raw `btrfs subvolume` commands by hand, [Snapper](https://github.com/openSUSE/snapper) is a small tool that manages the whole snapshot lifecycle: creating them, storing them tidily, pruning old ones, and automatically bracketing every `apt` operation with a snapshot pair.
|
|
|
|
```bash [Terminal]
|
|
sudo apt install snapper
|
|
sudo snapper -c root create-config /
|
|
```
|
|
|
|
`create-config` registers a config named `root` for the `/` filesystem, and creates a dedicated `.snapshots` subvolume under it to store every snapshot, locked down to root. No `@`/`@home` subvolume split needed, this works fine on the plain single-subvolume layout the manual partitioning above just created.
|
|
|
|
Turn the config on by editing `/etc/default/snapper`:
|
|
|
|
```properties [/etc/default/snapper]
|
|
SNAPPER_CONFIGS="root"
|
|
```
|
|
|
|
Then enable the timers that run Snapper's periodic snapshots and cleanup:
|
|
|
|
```bash [Terminal]
|
|
sudo systemctl enable --now snapper-timeline.timer snapper-cleanup.timer
|
|
```
|
|
|
|
`snapper-timeline.timer` fires **every hour** by default, that's fixed by the unit itself, not by a config option. Each run takes one snapshot and files it under the appropriate bucket (hourly, daily, monthly, yearly); the `TIMELINE_LIMIT_*` settings covered further down only control how many of each bucket survive, not how often a snapshot is taken. `snapper-cleanup.timer` runs once a day (plus once 10 minutes after boot) to enforce those limits.
|
|
|
|
::tip
|
|
✨ To take timeline snapshots at a different frequency, for example every 6 hours instead of every hour, override the timer instead of editing the package's unit file directly:
|
|
|
|
```bash [Terminal]
|
|
sudo systemctl edit snapper-timeline.timer
|
|
```
|
|
|
|
```ini [override.conf]
|
|
[Timer]
|
|
OnCalendar=
|
|
OnCalendar=*-*-* 0/6:00:00
|
|
```
|
|
|
|
The empty `OnCalendar=` line clears the packaged `hourly` value first, since systemd otherwise adds new `OnCalendar` lines to the existing ones instead of replacing them. Apply it with `sudo systemctl daemon-reload && sudo systemctl restart snapper-timeline.timer`.
|
|
::
|
|
|
|
::note
|
|
|
|
Debian's package also ships an APT hook (`/etc/apt/apt.conf.d/80snapper`) that kicks in the moment a config is listed in `SNAPPER_CONFIGS`: from now on, every `apt install`, `apt upgrade` or `apt full-upgrade` automatically gets a snapshot right before and right after it runs, with zero extra effort on your part.
|
|
::
|
|
|
|
## Taking a snapshot
|
|
For anything outside apt, like editing a systemd unit or a Docker Compose file, take one yourself with a description that will actually mean something later:
|
|
|
|
```bash [Terminal]
|
|
sudo snapper create --description "before compose change on swag"
|
|
```
|
|
|
|
List every snapshot with:
|
|
|
|
```bash [Terminal]
|
|
sudo snapper list
|
|
```
|
|
|
|
```console [Output]
|
|
# | Type | Pre # | Date | Description
|
|
---+--------+-------+--------------------------+---------------------------------
|
|
0 | single | | | current
|
|
1 | single | | Tue 09 Sep 2026 18:30:00 | before compose change on swag
|
|
2 | pre | | Tue 09 Sep 2026 19:00:01 | apt install unattended-upgrades
|
|
3 | post | 2 | Tue 09 Sep 2026 19:00:14 | apt install unattended-upgrades
|
|
```
|
|
|
|
## Restoring files from a snapshot
|
|
Snapper keeps the full state of the filesystem at snapshot time under `/.snapshots/<number>/snapshot`, browsable like any other folder:
|
|
|
|
```bash [Terminal]
|
|
ls /.snapshots/1/snapshot/etc/ssh/
|
|
```
|
|
|
|
To see exactly what changed between a snapshot and the live system (`0` always means "current") before touching anything:
|
|
|
|
```bash [Terminal]
|
|
sudo snapper status 1..0
|
|
```
|
|
|
|
That prints every file created, modified or deleted since. To undo those changes automatically:
|
|
|
|
```bash [Terminal]
|
|
sudo snapper undochange 1..0
|
|
```
|
|
|
|
Or restore a single file by hand, which is often the safer, more surgical choice:
|
|
|
|
```bash [Terminal]
|
|
sudo cp -a /.snapshots/1/snapshot/etc/ssh/sshd_config /etc/ssh/sshd_config
|
|
```
|
|
|
|
::note
|
|
|
|
This covers the vast majority of real homelab accidents: a bad config, a deleted file, a package upgrade that broke one thing. Snapper can also `rollback` the entire root filesystem to an earlier snapshot (it creates a new default subvolume and boots into it on the next restart), for a system in a genuinely bad state rather than just missing one file. It's a more delicate, less common operation, worth testing once on a machine you don't mind rebooting before you actually need it for real. And if the disk itself is the problem, that's exactly the scenario your off-site [Backrest](/serveex/advanced/backrest) backup is for anyway.
|
|
::
|
|
|
|
## Managing and pruning snapshots
|
|
Snapshots are cheap, not free: the moment the live filesystem diverges from one, the old blocks it still references stick around. Left unchecked, months of snapshots on a server that changes a lot (container images, logs) can quietly eat real disk space.
|
|
|
|
The `snapper-cleanup.timer` enabled above already prunes automatically, based on the config at `/etc/snapper/configs/root`:
|
|
|
|
| Setting | Default | What it does |
|
|
|---------|---------|---------------|
|
|
| `TIMELINE_LIMIT_HOURLY` / `_DAILY` / `_MONTHLY` / `_YEARLY` | `10` | How many timeline snapshots of each granularity to keep |
|
|
| `TIMELINE_LIMIT_WEEKLY` | `0` | Off by default |
|
|
| `NUMBER_LIMIT` | `50` | How many manual (`single`) snapshots to keep |
|
|
|
|
Adjust these to taste, then delete one immediately by hand if you need the space back right now:
|
|
|
|
```bash [Terminal]
|
|
sudo snapper delete 1
|
|
```
|
|
|
|
Check actual disk usage with `df -h /`: the numbers above only cap how many snapshots exist, not the space a very active server can still burn through between cleanups.
|
|
|
|
That's it: a rolling, automatic undo button for your server, quietly bracketing every update and pruning itself, running alongside the real backups Backrest is already taking off-site.
|