Documentation
Your Backup Is a Git Clone. Until It Isn't.
One of the appealing properties of storing application data in Git is that backups initially look almost too easy.
If the repository contains the state, then make another copy of the repository.
Done.
There is no database dump format to remember, no application-specific export command, and no collection of unrelated directories that need to be reconstructed in the right order.
For Everlock, this is a real advantage. A repository is not merely a source-code container sitting behind the application. It is the durable representation of the data itself.
That leads to an attractive equation:
backup = another Git repository
And for a while, that equation is useful.
But a replica and a backup are not quite the same thing.
The difference only becomes obvious when something goes wrong.
Git makes replication unusually cheap
Git is already designed to copy repository state between machines.
Objects are content-addressed. Transfers can avoid sending objects the destination already has. Refs describe the currently named states. Integrity checks are built deeply into the object model.
That gives a Git-backed application a replication mechanism almost for free.
A second machine can maintain another copy of a repository without needing to understand what the application stores inside it.
For Everlock, that is exactly the kind of property I want.
The backup system shouldn't need to know what a photo library looks like.
It shouldn't need to understand calendars, configuration, mail, or whatever else happens to be stored in the repository.
It should be able to say:
source repository
|
v
copy Git state
|
v
backup repository
The less application-specific the backup mechanism is, the better.
Then someone deletes something
Suppose the primary repository contains:
refs/heads/main -> A
and the backup contains the same state:
refs/heads/main -> A
Now the primary advances:
refs/heads/main -> B
The backup follows:
refs/heads/main -> B
Perfect.
But now imagine a bad update.
A ref is deleted.
Or moved to the wrong commit.
Or an administrator accidentally replaces valid state with corrupted state.
If the backup system faithfully synchronizes the repository, it may faithfully synchronize the mistake too.
primary: bad state
|
v
sync
|
v
backup: bad state
Replication has done its job.
Backup has not.
A replica answers a different question
A replica is useful when the question is:
What does the repository look like now?
A backup becomes useful when the question is:
What did the repository look like before something went wrong?
Those are different requirements.
A continuously synchronized Git mirror is excellent at preserving current repository state on another machine.
It protects against things such as the primary disk disappearing.
If machine A dies completely, machine B still has the objects and refs.
But it doesn't necessarily protect against logical destruction.
If machine A says:
delete this ref
and machine B immediately agrees, the second machine hasn't preserved an earlier recovery point.
It has preserved the latest truth, including the mistake.
Git history helps, but only sometimes
At this point it is tempting to say that Git already has history.
Why would we need snapshots?
If a file is deleted in a normal commit, Git history does exactly what we want.
The old file remains in the parent commit:
A -> B -> C
\
file deleted in C
Recovering the previous state is easy.
That is one of the reasons Git is attractive as a storage layer in the first place.
But Git history only protects changes represented as history.
Refs can be rewritten.
Commits can become unreachable.
Repositories can be damaged.
Administrative operations can remove data outside the normal application workflow.
Garbage collection can eventually make unreachable objects disappear.
And a bug can modify repository state in a way that the application itself considers legitimate.
Git gives us excellent version history.
It does not make an independent backup policy unnecessary.
The backup needs its own notion of time
The useful change for my Everlock backups was to stop treating the backup tree as merely another synchronized destination.
The copy still uses Git.
But the filesystem underneath it also provides snapshots.
With Btrfs, the rough model becomes:
primary Git repositories
|
v
synchronized copy
|
v
Btrfs backup tree
|
+--> snapshot yesterday
+--> snapshot last week
+--> snapshot last month
Now there are two different histories.
Git records application history.
Btrfs records backup history.
They overlap, but they are not trying to solve the same problem.
That separation is useful.
Why snapshot the repositories instead of exporting them?
Another approach would be to periodically create archive files:
everlock-2026-09-01.tar.zst
everlock-2026-09-02.tar.zst
everlock-2026-09-03.tar.zst
That works.
But filesystem snapshots have a useful property for a repository tree: unchanged data doesn't need to be copied again in the same way a completely independent archive would.
The backup can remain directly usable as a normal directory tree while snapshots preserve older states efficiently.
The current copy might look like:
/backup/git/
photos.git/
calendar.git/
system.git/
A snapshot preserves that entire state at a point in time.
Recovery doesn't require inventing a new Everlock-specific format.
It is still Git repositories all the way down.
The layers now have different jobs
Once snapshots are involved, the design becomes easier to reason about if each layer has one responsibility.
Everlock
|
| application changes
v
Git
|
| repository synchronization
v
backup filesystem
|
| point-in-time preservation
v
Btrfs snapshots
Git answers:
What changes happened to the data?
Repository synchronization answers:
Do I have another copy if the primary storage disappears?
Snapshots answer:
Can I recover the copy as it existed at an earlier time?
Trying to make any one of these mechanisms answer all three questions creates unnecessary complexity.
A backup shouldn't depend on the application being healthy
There is another property I like about this arrangement.
Everlock doesn't need to be able to start for the backup to be useful.
Imagine a software update introduces a bug that prevents the server from reading its repositories correctly.
The recovery process shouldn't begin with:
First get the broken application working so it can export its data.
The backup consists of Git repositories.
Git tooling can inspect them.
The filesystem can restore an earlier snapshot.
Everlock is useful for interpreting the application-level data, but it isn't the gatekeeper to recovering the storage.
That is a very useful property for self-hosted software.
The thing being backed up shouldn't also be the only tool capable of restoring the backup.
Bare repositories make this especially boring
Everlock works directly with Git repositories rather than relying on checked-out working trees for its storage operations.
That makes the backup target pleasantly uninteresting.
There is no need to reproduce a working directory with temporary files, ignored files, build artifacts, and partially modified state.
The durable unit is the repository itself.
A backup job can operate on a tree of repositories and copy repository state into another tree.
Conceptually:
/var/lib/everlock/
photos.git
calendar.git
system.git
|
v
/backup/everlock/
photos.git
calendar.git
system.git
Then the filesystem snapshot captures the collection as another recovery point.
There is very little application logic involved.
That is exactly what I want from backup infrastructure.
Synchronization still needs care
"Copy the Git repository" sounds simple, but repository state can change while it is being copied.
Objects may be added.
Refs may move.
Maintenance may repack objects.
So a backup process still needs to think about consistency.
The important point is not that Git magically removes every backup problem.
It is that the backup can use Git-aware operations for repository synchronization and filesystem-aware operations for point-in-time retention.
Those tools understand their respective layers better than Everlock-specific backup code would.
This also keeps the application from accumulating a second, parallel persistence implementation.
Snapshots are not off-site backups
There is an obvious limit to filesystem snapshots.
If the snapshots live on the same physical machine as the primary data, losing that machine loses both.
Even if the backup disk is separate, theft, fire, power damage, or operator error can still affect the entire system.
So the useful hierarchy is closer to:
Git history
|
v
local replica
|
v
point-in-time snapshots
|
v
independent/off-site copy
Each layer protects against a different class of failure.
Git history protects against many application-level mistakes.
A replica protects against loss of the primary copy.
Snapshots protect against synchronization of bad state.
An independent copy protects against losing the entire local environment.
Calling any single one of these "the backup" hides those differences.
Test the restore, not the backup
There is also a slightly uncomfortable truth about backup systems.
Successfully copying data proves that data was copied.
It doesn't prove that recovery works.
For Git repositories, a restore test can be wonderfully concrete.
Take a snapshot.
Restore a repository somewhere else.
Check its refs.
Check its objects.
Clone or fetch from it.
Start Everlock against the restored data if application-level verification is needed.
The test should answer:
Can this copy become the primary?
That is much stronger than:
Did last night's backup job exit with code 0?
A green systemd timer is reassuring.
A repository successfully recovered from yesterday's snapshot is evidence.
Git removes a lot of backup machinery, not the need for backups
Using Git as Everlock's storage layer does simplify backup design.
There is no proprietary database dump.
There is no special export representation.
There is no need to reconstruct state from several unrelated persistence systems.
The repository is already portable.
But portability is only one part of backup.
A synchronized Git repository gives me another current copy.
Git history gives me application-level history.
Filesystem snapshots give the backup itself history.
Those are complementary properties.
The equation therefore becomes a little less elegant:
backup != git clone
A better version is:
recoverable backup =
Git repository
+ independent copy
+ point-in-time history
+ tested restore
That is more machinery than git clone.
But it still has one property I care about enormously: none of that machinery needs to understand Everlock's data model.
The backup remains boring infrastructure operating on ordinary Git repositories.
And boring backups are much easier to trust.