This site is the project. Not just the design or the writing — the whole pipeline that produces it.
What It Is
A static site built from markdown files, generated by a PHP build script, deployed via FTP from a Docker Swarm container to a cheap shared host. No WordPress, no CI service. Just files.
How It Works
Content lives as .md files with YAML frontmatter on an NFS share:
Running the build script scans those files, updates a SQLite database, and regenerates all HTML — index, blog listing, project listing, and individual post pages. Parsedown handles the Markdown rendering.
Deploying runs lftp mirror to push everything in /var/www/html to the remote host via FTP, excluding PHP files and local build artifacts.
The Stack
Runtime: PHP 8.3 on Apache in a Docker Swarm service. Persistent state (SQLite DB, content files) lives on NFS so it survives container restarts.
Build: Custom PHP script — no dependencies outside of Parsedown and standard library functions. Generates clean semantic HTML with no JavaScript.
Deploy: lftp mirror --reverse uploads only changed files. A local pureftpd container acts as an FTP test target during development before pushing to the real host.
Admin panel: A dark-themed PHP control panel only accessible on my home network — shows content stats, config verification, and streams build/deploy output in real time. Not uploaded to the public host.
Design
Editorial minimalism. Cormorant Garamond for display text, Jost for UI. Warm neutral grays, a single muted slate-blue accent, generous negative space. Zero JavaScript on the public site.
The admin panel goes the opposite direction — dark terminal aesthetic, monospace throughout, blinking cursor.
What I Learned
Setting up the FTP pipeline was substantially more difficult than expected. Docker Swarm has silent limitations around privilege and security options that aren't well documented. The ftp:ignore-pasv-address lftp setting is the kind of thing you only find after hours of debugging passive mode failures on overlay networks.
The static generator approach has been worth it. No attack surface, no database on the public host, sub-100ms page loads, and the whole thing fits in a single Docker image I fully understand.