Files

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.