After years of using Middleman to generate my blog at garron.me — which had come from WordPress for a short while and Jekyll for a longer one — I made the decision to move to something simpler. I have less time these days and maintaining it was becoming impossible. I had the whole stack installed in a WSL environment on a Windows machine I almost never had access to. On top of that, the blog was offline for a couple of years because of exactly that: I accidentally deleted the Linode server where it ran and could not recreate it.

Then one day, as a way to try out Claude Code, I pulled the repo from GitHub and with Claude's help managed to get it running again inside a Docker container and published as a static site. But the workflow for creating new posts was not quite working for me. I no longer wanted to deal with Ruby gems and all of that. I also had garron.blog running on blot.im, and I wanted something where I could manage all three blogs from a single place. Three blogs, two sites — garron.me has two blogs separated by folders, /en/ and /es/.

Building the engine

I had installed Claude Code to develop an app for work — that is a different story — but I took the chance and asked it to write PHP code that could parse my Markdown files and generate the sites. I asked for Simple.css across all three blogs, and a Jekyll-style template system with nested layout files, so I could have full control over the templates.

I needed everything in Docker and running behind Caddy. We went through sprints where we iterated on ideas during development and testing. I have some programming background but I am not an expert. But, as incredible as it sounds, the objective was achieved. I cannot say whether it would be useful to others or just to me, nor whether it is a correct implementation or not — but I can say it works. And it has several features that are specifically useful to me.

The URLs follow the file paths in the filesystem, but the YAML front matter has a url: key for cases where that is no longer desired. The URLs keep the .html extension — ugly URLs, you might say, as opposed to pretty or clean URLs — because I come from the old school, from a time when websites were literally HTML files sitting on a disk, and the URL included the file extension.

The ten sprints

The development took ten sprints. Not sprints in the corporate sense of the word — no planning meetings, no story points — just short iterations where I described what I wanted in plain language and Claude Code implemented it, documented it, and tested it within the same flow.

Sprint 1 — The skeleton: Docker-only from day one. Three sites in config/sites.yaml, each with its own source directory, output, and base URL.

Sprint 2 — The real engine: Three PHP classes brought everything to life: FrontMatterParser (separates YAML from Markdown), LayoutRenderer (resolves the nested layout chain), and Builder (orchestrates everything). By the end of this sprint, all three sites were generating real static HTML.

Sprint 3 — More than pages: The builder learned to generate derived outputs: posts index, RSS feed, and sitemap. This required a two-pass build — collect all files first, then render — because the index needs to know about every post before writing anything.

Sprint 4 — Simple.css and styling strategy: The placeholder CSS was replaced with Simple.css. The policy was set: zero JavaScript on public pages.

Sprint 5 — Domain-level shared assets: garron.me/en and garron.me/es share the same domain, so they needed to share the same CSS and images instead of duplicating them. Resolved with two new keys in sites.yaml.

Sprint 6 — The admin: The biggest part of the project. A full PHP web interface to manage all three blogs from one place: create posts, edit drafts, upload images, trigger a build, and commit and push to git. Without the admin, the workflow would have been editing Markdown files by hand and running Docker commands — functional, but not what I wanted.

Sprint 7 — Cleanup and production: Utility commands (clean to wipe the output), build timing, and a production Caddyfile with basic auth for the admin.

Sprint 8 — Watch mode and pagination: The builder learned to watch for file changes and rebuild automatically — useful during development. Pagination was also added to the posts index. Sprint 8 was declared feature-complete.

Sprints 9 and 10 — Final polish: Two more sprints to tie up loose ends: fully isolated per-domain assets so garron.me and garron.blog can have different stylesheets without conflicting, and a migrate command to normalize the front matter in my old Middleman posts to the new format.

What I found most interesting about the process was not the code itself but the way of working: I described the problem or the goal in plain language, Claude Code designed the solution, implemented it, and documented the architectural decisions in a results file per sprint. If something did not feel right or did not work as expected, I said so and it was adjusted. I did not write a single line of PHP, but I made all the important decisions: what tools to use, which features to include, what complexity to avoid.

Conclusion

I ended up with software that is useful to me. I did nothing more than give instructions to the AI, test the functionality step by step, and make the design decisions. In the end it is like an architect and a builder working as a duo: I define the path and Claude builds the app. I had fun, and now I have something that makes creating new posts easy.

I do not know if this is useful to anyone else. The code is available on GitHub at github.com/ggarron/phile (the admin interface is not included in the public repo), if anyone wants to take a look, use it as a starting point, or just see how it turned out. I make no promises that it is an exemplary piece of software — but it works, and for me that is enough.