Use Snapper instead of a custom script for BTRFS snapshots
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: BTRFS Snapshots
|
||||
description: Format Debian's root filesystem with BTRFS and use snapshots to instantly roll back a risky update or config change, as a local complement to Backrest's off-site 3-2-1 backups.
|
||||
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.
|
||||
---
|
||||
|
||||
|
||||
@@ -50,118 +50,102 @@ Give the partition the rest of the disk (minus a small EFI partition if you're o
|
||||
|
||||
Everything else in the [installer](/serveex/core/installation#install-debian) stays the same.
|
||||
|
||||
## Taking a snapshot
|
||||
A snapshot is created with a single command, and completes instantly no matter how much data is on the disk, since Btrfs only starts copying blocks the moment something actually changes (copy-on-write):
|
||||
## 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 mkdir -p /.snapshots
|
||||
sudo btrfs subvolume snapshot -r / /.snapshots/$(date +%F_%H-%M-%S)
|
||||
sudo apt install snapper
|
||||
sudo snapper -c root create-config /
|
||||
```
|
||||
|
||||
- `-r` makes it **read-only**, which is what you want for a safety net: nothing, including you by accident, can modify a snapshot after the fact.
|
||||
- Storing snapshots under `/.snapshots` keeps them out of the way, and Btrfs is smart enough not to recurse into a subvolume the next time you snapshot `/` itself, so snapshots never end up containing older snapshots.
|
||||
`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.
|
||||
|
||||
Take the habit of running this before anything that could go wrong: `sudo apt full-upgrade`, editing a systemd unit, or a Docker Compose change that touches something outside `/srv/docker`.
|
||||
Turn the config on by editing `/etc/default/snapper`:
|
||||
|
||||
::tip
|
||||
✨ Give the snapshot a name that means something instead of just a timestamp, for example `/.snapshots/before-upgrade-2026-09-09`, so you know why it's there when you find it three weeks later.
|
||||
::
|
||||
```properties [/etc/default/snapper]
|
||||
SNAPPER_CONFIGS="root"
|
||||
```
|
||||
|
||||
## Restoring files from a snapshot
|
||||
A snapshot is just a folder: everything the filesystem looked like at that instant, browsable like any other directory.
|
||||
Then enable the timers that run Snapper's periodic snapshots and cleanup:
|
||||
|
||||
```bash [Terminal]
|
||||
ls /.snapshots/2026-09-09_18-30-00/etc/ssh/
|
||||
sudo systemctl enable --now snapper-timeline.timer snapper-cleanup.timer
|
||||
```
|
||||
|
||||
To undo a mistake, copy the file (or folder) back from the snapshot over the current one:
|
||||
|
||||
```bash [Terminal]
|
||||
sudo cp -a /.snapshots/2026-09-09_18-30-00/etc/ssh/sshd_config /etc/ssh/sshd_config
|
||||
```
|
||||
|
||||
`-a` preserves permissions and ownership, which matters for anything under `/etc`.
|
||||
|
||||
::note
|
||||
|
||||
This covers the vast majority of real homelab accidents: a bad config, a deleted file, a package upgrade that broke one thing. Rolling back the __entire__ root filesystem (for a system that no longer boots at all) is also possible, by renaming subvolumes from a live USB, but it's a more delicate, less common operation. The [Btrfs documentation](https://btrfs.readthedocs.io/en/latest/Subvolumes.html) covers it if you ever need it, and honestly, if it comes to that, this is exactly the scenario your off-site [Backrest](/serveex/advanced/backrest) backup is for anyway.
|
||||
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.
|
||||
::
|
||||
|
||||
## Managing snapshots
|
||||
## 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 btrfs subvolume list /
|
||||
sudo snapper create --description "before compose change on swag"
|
||||
```
|
||||
|
||||
Lists every subvolume on the filesystem, snapshots included, each with its own ID. Delete one you no longer need with:
|
||||
List every snapshot with:
|
||||
|
||||
```bash [Terminal]
|
||||
sudo btrfs subvolume delete /.snapshots/2026-09-09_18-30-00
|
||||
sudo snapper list
|
||||
```
|
||||
|
||||
::caution
|
||||
```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
|
||||
```
|
||||
|
||||
Snapshots are cheap, not free: the moment the live filesystem diverges from a snapshot, the old blocks stick around for as long as the snapshot references them. A pile of old snapshots on a server that changes a lot (container images, logs) can quietly eat real disk space. Check with `df -h /` and prune what you don't need anymore.
|
||||
## 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.
|
||||
::
|
||||
|
||||
## Automating snapshots
|
||||
Rather than remembering to run the command by hand, a small script plus a systemd timer takes one automatically and prunes old ones:
|
||||
## 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 nano /usr/local/bin/btrfs-snapshot.sh
|
||||
sudo snapper delete 1
|
||||
```
|
||||
|
||||
```bash [btrfs-snapshot.sh]
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /.snapshots
|
||||
btrfs subvolume snapshot -r / "/.snapshots/$(date +%F_%H-%M-%S)"
|
||||
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.
|
||||
|
||||
# Keep only the 7 most recent snapshots
|
||||
cd /.snapshots
|
||||
ls -1 | sort | head -n -7 | while read -r old; do
|
||||
btrfs subvolume delete "$old"
|
||||
done
|
||||
```
|
||||
|
||||
```bash [Terminal]
|
||||
sudo chmod +x /usr/local/bin/btrfs-snapshot.sh
|
||||
```
|
||||
|
||||
```bash [Terminal]
|
||||
sudo nano /etc/systemd/system/btrfs-snapshot.service
|
||||
```
|
||||
|
||||
```ini [btrfs-snapshot.service]
|
||||
[Unit]
|
||||
Description=Take a Btrfs root snapshot
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/btrfs-snapshot.sh
|
||||
```
|
||||
|
||||
```bash [Terminal]
|
||||
sudo nano /etc/systemd/system/btrfs-snapshot.timer
|
||||
```
|
||||
|
||||
```ini [btrfs-snapshot.timer]
|
||||
[Unit]
|
||||
Description=Daily Btrfs root snapshot
|
||||
|
||||
[Timer]
|
||||
OnCalendar=daily
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
```bash [Terminal]
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now btrfs-snapshot.timer
|
||||
```
|
||||
|
||||
Check it's scheduled with `systemctl list-timers`, and trigger one right away to test it with `sudo systemctl start btrfs-snapshot.service`.
|
||||
|
||||
That's it: a rolling, automatic undo button for your server, running quietly alongside the real backups Backrest is already taking off-site.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user