The Daily
A note a day on APIs and the realities of shipping web applications. Stuff I've actually learned across 25 years of doing the work.
Shipping Laravel Logs to Self-Hosted Loki on the Same Box
You don't need Grafana Cloud or a separate observability server. Here's how I run Loki + Grafana on the same box as Laravel and actually sleep at night.
Loki is not Elasticsearch. That's the first thing worth understanding, because if you approach it expecting Elasticsearch's full-text indexing model, you'll either fight it or give up. Once I stopped fighting it, I got a log aggregation stack running on the same $40 VPS as a Laravel app — no Grafana Cloud account, no…
Read the daily →Restic + Cloudflare R2: Offsite Backups I Actually Trust
I've been burned by backup strategies that looked solid until they weren't. Restic and R2 fixed that for me — encrypted, versioned, and cheap enough to run hourly.
Three years ago I had a client lose a week of production data. Their hosting provider had backups — on the same SAN that failed. Lesson learned the hard way: backup infrastructure that shares fate with the thing it's backing up is not backup infrastructure. Since then I've standardized on restic pointed at Cloudflare…
Read the daily →OPcache in Docker: The Default Settings That'll Hurt You at Peak Load
PHP's OPcache defaults were written for a different era. In a Dockerized Laravel app under real traffic, they'll quietly kill your performance at the worst possible moment.
I've watched a Laravel app crater under Black Friday traffic because the OPcache was set to 128MB and the app had grown to the point where it couldn't fit. No alarm went off. No obvious error. Just PHP silently falling back to compiling files on every request while the CPU pegged at 100% and response times went from…
Read the daily →Adding a Read Replica Before Your Single VM Catches Fire
When your single-VM Postgres finally hits its ceiling, a read replica buys you time. Here's the actual runbook — migrations, Laravel config, and the lag trap nobody warns you about.
The moment you've been ignoring finally arrives: is packed, your query logs are full of sequential scans, and the client is asking why the dashboard takes eight seconds to load. You don't need a full rewrite. You need to stop sending read traffic to the same machine that's handling writes. A read replica is the right…
Read the daily →InnoDB Buffer Pool Sizing on a Shared VM: Stop the Swap
Get the buffer pool wrong on a shared VM and you'll trade database performance for PHP-FPM starvation or constant swapping. Here's the formula I use.
The default InnoDB buffer pool size is 128MB. On a dedicated database server with 64GB of RAM, that's laughably conservative. On a shared 2GB VM running MariaDB, PHP-FPM, Nginx, and a Laravel queue worker, it might actually be the right ballpark — or it might be half the reason your site is swapping at 2am under load…
Read the daily →