Post
Why I like FileSystem-as-Database
I keep ending up in the same place: treat the filesystem as the database.
Not as a joke, and not as a stopgap until I “do it properly”. For content — posts, pages, notes — the files are the real thing. If the app needs filters or search, I put an index on top.
This site works that way. Markdown under content/, frontmatter for the metadata, Nuxt Content stuffing it into SQLite so lookups are fast. The files are the record. The database is just a derived view.
What I mean by FileSystem-as-Database
In a normal CMS, the truth sits in Postgres or MySQL. You write into forms. Export is something you deal with later. Diffs are mostly useless.
Filesystem-first looks more like this:
- Write a
.mdfile (or JSON, YAML, TOML — whatever text). - Keep it on disk somewhere you can back up and mount.
- Index it into SQLite / search / caches for the app.
- Treat the file as canonical — if the index disagrees, rebuild the index.
That last bit is the whole point. The filesystem is not a cache of the database. The database is a cache of the filesystem.
Why human-readable files matter
This is the bit I actually care about.
A blog post is not a row. It is something I want to open at 01:00 in whatever editor I already have running, fix a typo, and move on. When it is Markdown:
- I can read it without a UI
- I can diff it in Git (when I bother to version it)
- I can grep it with tools I already use
- I can copy a folder and know I have the whole post, not a dump that needs a schema to make sense of
- I can edit it in Studio, VS Code, Obsidian, or
sed— same bytes
That is what ownership feels like for me. Binary blobs and proprietary export formats do not.
Frontmatter fills the gap: enough structure for tags, dates, visibility, layout — still text, still greppable, still mergeable if you have to.
Advantages
Portability
A folder of Markdown just… moves. New host, new framework, ten years later — the writing still opens. Migrating is “point something at a directory”, not digging through a dead SaaS dump.
Tooling for free
ls, rg, diff, rsync, tar, editors, linters, previews. You get a CMS CLI without writing one. That is a lot of work you never have to do.
Honest backups
Back up the volume or the directory. Restore means the files are back. You are not hoping yesterday’s dump still matches tomorrow’s schema.
(Ask me how I felt when I almost wiped content/ with a clever Git merge. The lesson was not “don’t use files”. It was “these files are production data — treat the mount that way”.)
Easier to reason about
Source of truth → index → UI. When something looks wrong on the site, open the file. Usually that is enough.
Fits static-ish and hybrid sites
Nuxt Content, VitePress, Astro, Eleventy — they all work this way. You can bolt on a Studio UI later without giving up the files.
Diffs and review (when versioned)
Even if the live content sits outside Git, drafts and migrations can still be reviewed as text. Pull requests for content are doable. That is hard when everything is opaque CMS rows.
Disadvantages
This is not free. Worth saying that out loud.
Concurrent writes
Two people saving the same file is still last-write-wins unless you add locking, CRDTs, or a rule of “just don’t”. A real database handles that better out of the box.
Query power is borrowed
Sorting by date and filtering tags is fine once you index. Ad-hoc joins, analytics dashboards, heavy relational stuff — a pile of Markdown is not built for that. You either build a proper query layer or you hit a wall.
Scale has limits
Thousands of posts? Fine for most blogs. Millions of tiny records with messy relations? Use a database. Files are good for document-shaped content. Not everything.
Permissions and multi-tenancy
Filesystem ACLs and app-level roles (public / paid / private) can work, but you design that yourself. A hosted CMS usually ships with that story. You own the awkward edges.
Sync and deployment footguns
Bind mounts, Docker volumes, gitignore, CI — all of them can delete or diverge content if you are careless. Files make the risk visible. They do not remove it.
Index drift
If the app indexes into SQLite and someone edits files underneath, you need a sync story: watchers, rebuilds, or “restart and reindex”. The filesystem is truth. The index has to catch up.
How I use it here
Rough shape:
content/
posts/*.md ← canonical posts
pages/*.md ← canonical pages
.data/ ← auth + Content index (derived)
Editors (or Studio) write Markdown. Nuxt Content builds an index you can query. Public routes filter on status, visibility, tags, dates. The writing never had to live only inside a database cell.
That is the bet: files for the actual content, indexes when you need speed.
When I would not use it
I would not store shopping carts, sessions, or high-churn financial data as Markdown. I would not pretend a directory tree replaces Postgres for every SaaS.
I would use filesystem-first for:
- blogs and docs
- knowledge bases
- config and content that humans should be able to read
- anything where longevity and ownership matter more than multiplayer editing at scale
Closing
I like FileSystem-as-Database because the content stays mine: readable, portable, a bit boring in a good way. The database (or search index) can be clever. The writing should stay plain.
That is mostly it. Open the file, understand it, move on. That is still worth something.