A headless CMS that keeps your writing in files.
Content lives on disk as Markdown with YAML front matter. SQLite indexes it so it can be queried. A JSON API serves it. The admin is a separate app that talks to that same API, with no privileged access of its own.
"Lagom" is Swedish for the right amount. It is used here as a constraint rather than a slogan: when a feature can be argued either way, it stays out.
Almost every CMS puts your writing in a relational database. That was a reasonable choice in 2003 and it has been inherited ever since. It costs more than people notice: content in a format you cannot read, no meaningful diff of a change, backups that drift out of step with the code, and a database process to run.
Files give you the opposite and give up querying, which is the one thing the database was genuinely good at. LagomCMS keeps the files and builds an index over them, so the index is a cache rather than the only copy of anything. Delete it and it rebuilds in under a second.
That is not a novel idea. It is how a search engine relates to a filesystem. It just has not been the default here.
There is no theme system, no page builder, and no plugin marketplace. It does not render
your website. examples/starter-astro/ is a frontend to fork rather than a theme to
configure, and the core knows nothing about it.
Yes, if you are a developer self-hosting on modest hardware, you want content in git with a structured body and the model in code, and you value a CMS small enough to read end to end.
Probably not otherwise, and the alternatives are good ones:
- Payload for this shape with a community behind it.
- Kirby or Statamic for flat files with a decade of production behind them, if you will pay a licence.
- Sanity or Contentful if you would rather not run anything.
- WordPress if non-technical people need to publish today.
What this has that the flat-file three do not is a real index. Querying a few thousand documents over a directory tree is where they run out of road, and Statamic's answer was an Eloquent driver that moves content into a database, which gives up the thing you chose it for. Here the index is SQLite with FTS5 and it stays disposable.
What it has that the API-first three do not is a footprint. Strapi, Directus and Payload are Node plus a database. This is one container at about 20MB with no database process, which is the difference between fitting on a small VPS and not.
- OpenResty, no framework. Request handling is Lua on nginx.
- Content is one folder per document under
content/, Markdown plus front matter. - The index is SQLite, kept in step by inotify. It is derived and disposable.
- The body is parsed to a typed block array rather than an HTML blob, so a consumer can render it however it likes.
- The model is plain Lua tables in
collections/, reviewed and deployed like code. - Writes are hash guarded, so editing through the admin and through git can coexist.
22 endpoints under /api/v1, including filtering, full text search, media uploads, cookie
and token auth, and a generated OpenAPI document.
Requires Docker. On Fedora or another SELinux system nothing extra is needed; the compose
file carries the :z relabels.
make up # build and start on port 4702, wait for health
make health # what loaded, what is watching, how much memory
make spec # end to end checks against the running instance
make reload # pick up Lua changes without a restart
make backup # content/ and accounts; the index is derived and excluded
make down
make up also starts the Astro starter on 4715, which reads the API like any other
consumer and holds no credentials.
The admin is at http://localhost:4702/admin/. A password for admin@localhost is printed
once on first boot; set LAGOM_ADMIN_PASSWORD beforehand to choose your own.
content/, core/ and collections/ are bind mounted, so you edit with your normal editor
and the running instance picks it up. On Linux inotify events cross the bind mount, so that
is the real watch path rather than a polling approximation.
Version 0.1.0-dev. The architecture is written down in docs/SPEC.md and all of it runs. It serves https://dotmavriq.life in production, where the CMS holds steady at about 20MB resident.
That is one site run by the person who wrote it, which is a long way from proven. The test suite is end to end against a running instance rather than unit tests, because nearly every bug found so far has lived in a seam rather than in a function.
Deployment is documented in docs/deployment.md.
AGPL-3.0. If you run a modified version as a network service, the modifications are covered.