Blog

Manage content in files, not databases

Olivier Carrère
#Static Sites#Unix Philosophy#Docs-as-Code#Content Management#Git

Managing site content in plain files (Markdown, HTML, etc.) is an approach that leans on a very old — and very sensible — UNIX idea: everything is files. For many projects this is not just nostalgic; it’s practical. Below I explain why files often win over databases for content management, and how to work with them effectively.

The Unix principle — everything is a file

When content lives as files, you treat each article, page or asset as a first-class file in a folder tree. Filenames often act as slugs (the URL path). If you need a different URL, you can override it with a slug: field in the frontmatter. That simple mapping keeps things explicit and easy to reason about.

Example frontmatter + filename:

---
title: "How managing content in files is more efficient than in a database"
slug: "/files-vs-database"   # optional — filename can also be used
date: 2025-09-19
tags: [static-site, git, unix]
---

Your article body...

File: content/posts/files-vs-database.md

Productivity & tooling: use the tools you already know

Because content is files, you can use standard, battle-tested tools:

Files: A series of bottles to illustrate that you can edit the content of files without necessarily opening them.

These tools let you do bulk refactors, search-and-replace, or migrations in seconds — no complex admin UI needed.

Version control with Git: track, revert, branch

Storing content in files means it sits naturally in Git:

graph TD %% Nodes U1["Alice: VS Code"] U2["Bob: Notepad++"] F1["Local Repo (feature-branch)"] F2["Local Repo (feature-branch)"] ORIGIN["Remote Repo (GitHub)"] PR["Pull Request"] MAIN["Main Branch"] PUBLISH["Publish"] %% Flows U1 u1@--> F1 U2 u2@--> F2 F1 f1@-->|"push"| ORIGIN F2 f2@-->|"push"| ORIGIN ORIGIN f3@-->|"pull"| F1 ORIGIN f4@-->|"pull"| F2 ORIGIN --> PR --> MAIN --> PUBLISH f1@{ animation: slow } f2@{ animation: slow } f3@{ animation: slow } f4@{ animation: slow } u1@{ animation: slow } u2@{ animation: slow } %% Styling classDef user fill:#fbe9e4,stroke:#df9277,stroke-width:2px,color:#5a2e23,font-weight:bold,rx:8,ry:8; classDef local fill:#f6d1c5,stroke:#d97d61,stroke-width:2px,color:#4a241a,font-weight:bold,rx:8,ry:8; classDef remote fill:#efb9a6,stroke:#c96a52,stroke-width:2px,color:#3d1d15,font-weight:bold,rx:12,ry:12; classDef review fill:#f6d1c5,stroke:#b8573f,stroke-width:2px,color:#3d1d15,font-weight:bold,rx:8,ry:8; class U1,U2 user; class F1,F2 local; class ORIGIN remote; class PR review; class MAIN remote; class PUBLISH remote; linkStyle default stroke:#df9277,stroke-width:2px;

Feature Branch Workflow for Documentation

gitGraph TB: commit id:"doc feature C init" tag:"release 1.1" commit id:"doc feature C update" branch Alice checkout Alice commit id:"doc feature A draft" commit id:"doc feature A refactor" commit id:"doc feature A review" commit id:'Revert "doc feature A refactor"' type: REVERSE checkout main commit id:"refactor tables" commit id:"doc feature D init" branch Bob checkout Bob commit id:"doc feature B draft" commit id:"doc feature B review" checkout main commit id:"refactor images" merge Alice commit id:"doc feature D update" tag:"release 1.2" merge Bob commit id:"doc feature E init"

This Git workflow illustrates a documentation development process using feature branches. The main branch tracks official releases, while contributors create separate branches for their own work.

This workflow highlights parallel development, isolation of work in branches, and structured integration into main for tagged releases.

Security: fewer attack surfaces

No DB = no SQL injection. Plain files eliminate a class of vulnerabilities related to dynamic query building, mis-sanitized input, or poorly-configured database engines. There are still security considerations (XSS, server config, secret leakage), but the risk of a corrupted or malicious query is gone.

Cost: free for small projects on modern CDNs

Static sites (generated from files) can be hosted for free or very cheap on platforms like Vercel, Netlify, GitHub Pages — perfect for blogs, documentation, marketing sites. No database server bills, no complex infrastructure to manage.

Speed & SEO: ultra-fast delivery

When pre-built files are served from a CDN they’re delivered extremely quickly to users worldwide. Faster page loads improve user experience and SEO. Static assets are easy to cache and scale.

Reliability: simpler platform than LAMP stacks

A static-file setup has fewer moving pieces than a LAMP/LEMP stack:

Fewer components → fewer failure modes.

More control over content changes

Git-based workflows let you:

This creates an auditable chain from edit → review → publish.

Caveats: when a database might still be right

Files are great for many use cases, but databases make sense when you need:

If your project needs those, a hybrid approach (files for the majority of content + database for the dynamic parts) also works well.

Quick practical checklist to get started

  1. Keep each post/page as a Markdown file with YAML frontmatter.
  2. Use filenames as slugs; override with slug: when necessary.
  3. Put everything under Git and push to GitHub/GitLab.
  4. Configure a static site generator (Hugo, Jekyll, Eleventy, Next.js, etc.).
  5. Connect your repo to a CDN (Vercel/Netlify) for automatic builds on push.
  6. Use grep, sed, awk or small Python scripts for bulk edits.
  7. Review changes through PRs and git diff before deployment.

Managing content as files embraces simplicity, transparency and control. You gain powerful Unix tooling, Git-based workflows, better security against database-specific attacks, lower cost for small projects, and excellent performance when combined with CDNs — all reasons why files are often the more efficient choice over a database-driven CMS.

← Back to Blog