Post
Gitignore is not a backup — how I almost lost my content/
I thought I was being tidy.
This site keeps Markdown under content/. At some point that folder became runtime data: edited in Studio, mounted into Docker, not something I wanted in every commit. So a feature branch did the “grown-up” thing:
- stop tracking
content/ - add
/content/to.gitignore - merge into
main
I even have a small helper — call it merge-delete — that merges a branch and then deletes it. Convenient. Until the merge finished and my posts directory looked like this:
- one leftover post
- one homepage markdown file
- everything else gone from disk
Luckily I still had content.tar.gz. That is not a strategy. That is luck.
What actually deleted the files
Branch deletion did not wipe the data. The merge did.
When Git still tracked files under content/, those paths lived in the commit tree. The “untrack content/” commit does not only update .gitignore. It removes those paths from the tree — the same kind of change as deleting the files in that commit.
Merge that into main, and Git updates your working tree to match the result. Tracked files that no longer exist in the merged tree are removed from disk. After that, /content/ is ignored, so a later checkout will not bring them back. From Git’s point of view the job is done. From yours, the blog body just vanished.
Ignored-and-never-tracked files usually survive checkouts. The dangerous sequence is:
tracked → deleted in a commit → ignored
That is what merge-delete applied in one step.
Prevention checklist
1. Back up before you untrack
Before the commit that removes content/ from the index, take a copy Git cannot touch:
tar -czf ~/backups/content-$(date +%Y%m%d).tar.gz content/
Or rsync to another disk / object storage. Do this before git rm -r --cached content/ (or whatever untrack step you use), not after a hopeful merge.
2. Untrack without deleting the working copy — then verify
Typical safe-ish untrack:
git rm -r --cached content/
# add /content/ to .gitignore
git commit -m "Untrack content/ (runtime data)"
--cached drops paths from the index but leaves files on disk in that working tree. The trap appears later, when another clone or another branch checkout materialises the “files deleted from the tree” side of the same commit. So still treat the backup as mandatory.
After untracking, confirm what Git thinks:
git ls-files content | wc -l # should be 0
ls content/posts | wc -l # should still be your full set
git check-ignore -v content/posts
3. Keep runtime content outside the repo tree
If content/ is Docker runtime data, prefer a bind mount or named volume that is not the same folder Git checks out into.
Examples of the idea:
- host path:
~/blog-data/content→ container/app/content - named volume for content, repo checkout only for app code
Then a merge that rewrites the repo working tree cannot empty your only copy.
This project’s .gitignore already scopes that intent with a leading slash:
/volumes/
/content/
so only the repo-root paths are ignored — not something like app/components/content/. The ignore rule is fine. The missing piece was a durable home for the data besides the Git working tree.
4. Make merge helpers refuse “delete tree + ignore” surprises
If you keep a merge-delete alias, add a guard before merge:
# sketch — refuse if the branch removes a previously tracked content tree
if git diff --name-status main..."$branch" | grep -E '^D\tcontent/'; then
echo "Branch deletes content/ paths. Backup first, or abort."
exit 1
fi
Or at least print a loud warning and require a typed confirmation when the incoming diff deletes anything under content/ or other data roots.
Convenience scripts should be careful around data directories. Merge + delete is fine for feature code. It is hostile to “this folder used to be in Git and now it is not”.
5. Prefer volume restore over Git restore for day-to-day ops
Yes, you can often recover old tracked files from history:
git checkout '<commit-before-untrack>' -- content/
# or
git archive '<commit-before-untrack>' content | tar -x
That only works for whatever was committed. Studio edits, drafts, and uploads that never hit Git are gone unless you backed them up. Once /content/ is ignored, Git is no longer your CMS backup.
Treat recovery as:
- restore
content.tar.gz/ volume snapshot - only use
git checkout/git archiveas a secondary source for older published snapshots
6. Automate boring backups
Something small and boring beats another heroic evening:
- nightly
tarorrcloneof the content mount - retain a few dated archives
- optionally dump whatever else lives next to it (
data/, media buckets, SQLite, etc.)
If content is sacred enough to publish, it is sacred enough for a cron job.
The rule I am sticking to
Ignoring a folder does not mean “Git will leave my only copy alone forever.”
It means “Git will stop protecting this folder.”
So for runtime Markdown and media:
- backup first
- untrack second
- store the live copy on a volume or path outside checkout drama
- keep merge helpers from silently applying mass deletes under data roots
content.tar.gz saved me once. The goal is never needing that sentence again.