The web got heavy. Pages load megabytes of JavaScript to show a paragraph of text. Analytics, fonts, frameworks, polyfills. All of it to render a few words on the screen.
In Current Year™, we're no longer talking about the fact that we're all chasing the latest bells and whistles. This has been our norm for so long that it is no longer interesting to comment on. It is no longer cool to find the simple solutions because we obsessively chase the latest trends - not just fancy buzzwords but FEATURES! We want them all. Developers most of all are prone to this trend, yet we most of all should resist it. We have the power to simplify in software, and this power is stronger than ever now that we not only have the internet to look things up, but LLMs to make all the boring boilerplate for us. All we gotta do is present the idea, ensure it's correct, make a few adjustments and BAM!, that's a feature done.
Simplicity makes things easy. It creates peace of mind. Fewer moving parts means a tighter attack surface; fewer things that can go wrong. And what can go wrong, WILL go wrong - ask any software developer. This blog loads in one request per entry. One HTML file. No CSS file. No JS at all. Just this html document with a little in-document CSS.
If I were a purist, I would inline images to base64 so I can truly claim everything is loaded in a single page. And it is so easy to get lost on the righteous path of doing it The Right Way -- but there's something to be said about true simplicity, which is to not get swept up in The Cause - to not hold any beliefs too tight, and see the bigger picture beyond The Tech.
How else are you supposed to run the blog? If everything is just a simple HTML page; if you don't have a CMS server, do you manually wrangle the HTML every time you want to make a new post? Of course not! The posts are written in Markdown and a script generates the HTML files based on content and templates. You write the posts, adjust the templates if you feel like it, and the CMS watches for changes and regenerates automatically. BAM, instant static web blog. No HTML wrangling required. (:
The simplest way to get it live here and now is probably to rent some static web host, but then you're bound to THEIR toolkit and THEIR workflow. If you know how to use git, it's also super simple to use Github Pages (bonus: 100% free), but who wants to run a blog update through a git commit flow? This blog runs on a VPS (Hetzner) with Copyparty (incredibly underrated file server tool). The entire setup is handled by a single bash script that asks for a domain, username, and password, then configures everything: copyparty, the CMS, Let's Encrypt TLS, and an HTTP-to-HTTPS redirect.
The copyparty configuration ends up looking like this:
[global]
p: 443
smb-port: 445
smbw
vague-403
cert: /root/.config/copyparty/cert.pem
[/]
./fileroot/public
flags:
cachectl: no-store, max-age=0
accs:
h: *
A: MyUser
[/opendir]
./fileroot/public/opendir
accs:
r: *
[accounts]
MyUser: MyPassword
[/private]
./fileroot
accs:
A: MyUser
The / volume serves the public site (blog, landing page) as static HTML. /opendir is a public file browser powered by copyparty's UI. /private gives the authenticated user full access to the entire file tree via WebDAV.
The setup script also installs three systemd services: copyparty itself, the CMS watcher (which regenerates the blog whenever you edit a post), and a tiny HTTP-to-HTTPS redirect on port 80. Let's Encrypt handles TLS with automatic renewal — certbot hooks stop the redirect service, renew the cert, rebuild the combined PEM that copyparty expects, and start the redirect again.
This allows me to not only use a super easy file server as my blog host, but I can mount the file server as a network drive on my computer using rclone over WebDAV. The setup script even generates a client mount script with credentials filled in. I just write the markdown files in whatever program I want, hit save, and the CMS picks up the changes automatically.
The CMS runs as a service on the server, watching for file changes:
[Service]
ExecStart=dotnet CMS.cs --mode watch \
--posts fileroot/blog/posts \
--templates fileroot/blog/templates \
--images fileroot/blog/images \
--output fileroot/public/blog
It reads .txt files from the posts folder (first line: Title | Date, rest: markdown), applies templates, and generates static HTML. When a post is deleted, it cleans up the generated file too.
Now the blog automagically updates whenever I update the files on my mounted drive. If that isn't simple, I don't know what is.
Because we're running this on a server under our control, we can do something novel. We can own our own data. It does not belong to Facebook, or Medium, or any other service. It is our own. But that also means that we must take responsibility for databackup. And luckly it's dead simple because we kept everything simple and file-based.
You can really target any backup storage you want, but here I will point BorgBackup at Hezner's Storage Box:
# One-time setup:
# borg init --encryption=repokey ssh://uXXXXXX@uXXXXXX.your-storagebox.de:23/./backups/fileserver
/opt/borg/backup.sh:
#!/bin/bash
export BORG_REPO="ssh://uXXXXXX@uXXXXXX.your-storagebox.de:23/./backups/fileserver"
export BORG_PASSPHRASE="your-passphrase-here"
borg create --compression zstd "$BORG_REPO::{now}" /var/fileserver
borg prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6 "$BORG_REPO"
borg compact "$BORG_REPO"
/etc/systemd/system/borg-backup.service:
[Unit]
Description=Borg backup of /var/fileserver
[Service]
Type=oneshot
ExecStart=/opt/borg/backup.sh
/etc/systemd/system/borg-backup.timer:
[Unit]
Description=Run borg backup daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
The whole setup is a single script you can download from the open directory. Run it on a fresh VPS with a domain pointed at it, answer three questions, and you have a file server with a blog, TLS, WebDAV, and automatic updates.
Don't forget to change the default password ;)