<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>r3p.dev — Blogs</title>
		<link>https://r3p.dev/blogs</link>
		<description>Writing on technology, infrastructure, workflows, and things worth documenting.</description>
		<language>en</language>
		<lastBuildDate>Thu, 20 Aug 2026 00:00:00 GMT</lastBuildDate>
		<atom:link href="https://r3p.dev/rss.xml" rel="self" type="application/rss+xml" />
		<item>
			<title>Deploying UmimaClean: AdonisJS on a VPS with GitHub Actions</title>
			<link>https://r3p.dev/blogs/deploying-umimaclean</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/deploying-umimaclean</guid>
			<description>Taking a real AdonisJS 7 and Inertia application from a push on master to a running production service - what `node ace build` produces, a two-stage Containerfile, Postgres and the app as Quadlet units, a CI pipeline that builds to GHCR and deploys over SSH, and the proxy detail that silently breaks rate limiting.</description>
			<pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
			<category>adonisjs</category>
			<category>github-actions</category>
			<category>deployment</category>
			<category>podman</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 13: Testing an Inertia App with Japa</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-13</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-13</guid>
			<description>The final part - three suites split by what each proves, asserting on Inertia props instead of parsing HTML, factories with states that name business situations, organising tests by role because that is what breaks, and an honest account of what is not covered.</description>
			<pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
			<category>japa</category>
			<category>testing</category>
			<category>adonisjs</category>
			<category>playwright</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 12: Auth, Shield, Limiter, and Drive</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-12</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-12</guid>
			<description>The security layer in full - phone-number identity, why a signed-cookie session still has to be revocable, the passwordChangedAt trick, CSRF with a webhook exception, five rate limiters keyed on different things, private file storage with signed URLs, and the accounts that cannot be deleted.</description>
			<pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate>
			<category>adonisjs</category>
			<category>security</category>
			<category>authentication</category>
			<category>rate-limiting</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 11: Tailwind 4, Base UI, and Recharts</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-11</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-11</guid>
			<description>The frontend layer in detail - config-in-CSS and what disappeared with tailwind.config.js, why components are copied rather than installed, cva variants over prop soup, atomic-design folders that actually answer a question, and charts that theme themselves through CSS variables.</description>
			<pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate>
			<category>tailwindcss</category>
			<category>react</category>
			<category>base-ui</category>
			<category>recharts</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 10: ExcelJS, Exports, and the Timezone Bug Nobody Notices</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-10</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-10</guid>
			<description>Building spreadsheet exports an owner actually uses - why numbers must be numbers, the UTC-components bug that shifts every date by a day, a generic sheet builder that erases its own type parameter, frozen headers and autofilters, and why the export must never stop at the current page.</description>
			<pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate>
			<category>exceljs</category>
			<category>reporting</category>
			<category>adonisjs</category>
			<category>luxon</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 9: Fonnte, WhatsApp, and a Notification Channel With No Email</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-9</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-9</guid>
			<description>Building on WhatsApp because that is where the customers are - why there is no email field anywhere, the unofficial API trade-off, an endpoint that returns HTTP 200 to report failure, signed URLs that carry their own identity, and why reminders are sent by hand.</description>
			<pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate>
			<category>fonnte</category>
			<category>whatsapp</category>
			<category>adonisjs</category>
			<category>notifications</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 8: Midtrans and QRIS Payments End to End</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-8</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-8</guid>
			<description>Integrating an Indonesian payment gateway properly - Core API versus Snap, why the Midtrans order id cannot be your order number, the SHA-512 webhook signature that is the whole security of the flow, mapping five gateway statuses onto four of your own, and the escape hatch for the callback that never arrives.</description>
			<pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate>
			<category>midtrans</category>
			<category>payments</category>
			<category>qris</category>
			<category>adonisjs</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 7: Leaflet, Pinpointing, and Geospatial Rules</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-7</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-7</guid>
			<description>Building an address picker people can actually use on a phone - why the marker is fixed and the map moves, react-leaflet hooks, the tile layer licensing corner, Haversine distance for route ordering, and a service area that is not a circle.</description>
			<pubDate>Wed, 12 Aug 2026 00:00:00 GMT</pubDate>
			<category>leaflet</category>
			<category>react</category>
			<category>maps</category>
			<category>geospatial</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 6: Realtime with Transmit and Server-Sent Events</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-6</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-6</guid>
			<description>Why SSE beat WebSockets for a one-directional problem - how Transmit channels and authorisation work, the two channels this app has, why broadcasts carry stored values instead of labels, and what breaks when you run more than one process.</description>
			<pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate>
			<category>adonisjs</category>
			<category>transmit</category>
			<category>sse</category>
			<category>realtime</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 5: Transformers, Typed Routes, and Forms Without State</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-5</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-5</guid>
			<description>The layer between a model and a page - variants that encode who may see what, why depth is a property of the resource, Tuyau turning route names into compile-time checks, and Inertia 2 forms that need no useState at all.</description>
			<pubDate>Mon, 10 Aug 2026 00:00:00 GMT</pubDate>
			<category>inertia</category>
			<category>react</category>
			<category>adonisjs</category>
			<category>typescript</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 4: Inertia.js, or Deleting the API Layer</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-4</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-4</guid>
			<description>What it means to build a React app with no API - how Inertia visits work, shared props via middleware, flash messages and validation errors that arrive automatically, why SSR is configured but off, and the mobile app I gave up to get here.</description>
			<pubDate>Sun, 09 Aug 2026 00:00:00 GMT</pubDate>
			<category>inertia</category>
			<category>react</category>
			<category>adonisjs</category>
			<category>frontend</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 3: Lucid, PostgreSQL, and the Rules the Database Holds</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-3</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-3</guid>
			<description>Generated model schemas, migrations as the source of truth, partial unique indexes that enforce what application code cannot, RESTRICT as a history-preservation strategy, transactions around multi-table writes, and the numeric-returns-a-string gotcha that shipped broken exports.</description>
			<pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate>
			<category>adonisjs</category>
			<category>lucid</category>
			<category>postgresql</category>
			<category>database</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 2: VineJS and Validation as a Layer</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-2</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-2</guid>
			<description>Making validation composable and localised - a shared rule vocabulary, Indonesian messages registered once, a global transform that hands you Luxon dates, and the line between what a validator may know and what belongs in a service.</description>
			<pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
			<category>adonisjs</category>
			<category>vinejs</category>
			<category>validation</category>
			<category>typescript</category>
		</item>
		<item>
			<title>The UmimaClean Stack, Part 1: AdonisJS Structure, IoC, and the Request Lifecycle</title>
			<link>https://r3p.dev/blogs/umimaclean-stack-part-1</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-stack-part-1</guid>
			<description>How an AdonisJS 7 application is actually assembled - providers and preloads, the two middleware stacks and why the order matters, constructor dependency injection with no container registration, subpath imports, and validated environment variables.</description>
			<pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate>
			<category>adonisjs</category>
			<category>typescript</category>
			<category>nodejs</category>
			<category>architecture</category>
		</item>
		<item>
			<title>The Stack Behind UmimaClean: AdonisJS, Inertia, Leaflet, Midtrans</title>
			<link>https://r3p.dev/blogs/umimaclean-tech-stack</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-tech-stack</guid>
			<description>A tour of every library in a real production codebase and what each one actually cost - why AdonisJS over Express, Inertia instead of a REST API, Leaflet with Google tiles, Midtrans Core API over Snap, and the two data-type gotchas that broke my spreadsheet exports.</description>
			<pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate>
			<category>adonisjs</category>
			<category>inertia</category>
			<category>react</category>
			<category>leaflet</category>
		</item>
		<item>
			<title>Building an Operations Platform for a Shoe Cleaning Shop</title>
			<link>https://r3p.dev/blogs/umimaclean-operations-platform</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/umimaclean-operations-platform</guid>
			<description>What I learned building UmimaClean - why frozen prices are the most important decision in the schema, how a conditional UPDATE stops two staff driving to the same address, why a payment webhook needs an escape hatch, and the parts I would build differently.</description>
			<pubDate>Mon, 03 Aug 2026 00:00:00 GMT</pubDate>
			<category>adonisjs</category>
			<category>inertia</category>
			<category>react</category>
			<category>postgresql</category>
		</item>
		<item>
			<title>Caddy as a Reverse Proxy: Why I Chose It Over Nginx</title>
			<link>https://r3p.dev/blogs/caddy-reverse-proxy</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/caddy-reverse-proxy</guid>
			<description>What automatic HTTPS actually does under the hood, the Caddyfile syntax that matters, reverse proxy recipes for real applications, the gotchas that cost me time - and an honest account of where Nginx is still the better tool.</description>
			<pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate>
			<category>caddy</category>
			<category>nginx</category>
			<category>reverse-proxy</category>
			<category>https</category>
		</item>
		<item>
			<title>Podman Quadlet: Running Containers as systemd Services</title>
			<link>https://r3p.dev/blogs/podman-quadlet-systemd</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/podman-quadlet-systemd</guid>
			<description>A practical guide to Quadlet - where unit files go, what every section does, how networks and volumes are wired, building images declaratively, auto-updates, and the specific errors that waste the most time. Includes the rootless requirements nobody mentions.</description>
			<pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate>
			<category>podman</category>
			<category>quadlet</category>
			<category>systemd</category>
			<category>containers</category>
		</item>
		<item>
			<title>Why I Chose Podman Over Docker for My Server</title>
			<link>https://r3p.dev/blogs/why-podman-over-docker</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/why-podman-over-docker</guid>
			<description>A practical comparison after actually running both - the daemon versus fork-exec model, what rootless containers really protect you from, why systemd integration mattered more than I expected, and the cases where Docker is still the right answer.</description>
			<pubDate>Thu, 28 May 2026 00:00:00 GMT</pubDate>
			<category>podman</category>
			<category>docker</category>
			<category>containers</category>
			<category>linux</category>
		</item>
		<item>
			<title>How to Set Up a VPS for Beginners, from a Beginner (Part 5: Deploying Your App)</title>
			<link>https://r3p.dev/blogs/vps-setup-part-5</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/vps-setup-part-5</guid>
			<description>The final part of setting up my first VPS on Debian 13 Minimal. Building a container image in GitHub Actions, publishing it to GHCR, running it as a Quadlet service behind Caddy with no published ports, deploying on git push, then adding a second stack with a database and persistent storage.</description>
			<pubDate>Sun, 24 May 2026 00:00:00 GMT</pubDate>
			<category>vps</category>
			<category>podman</category>
			<category>quadlet</category>
			<category>deployment</category>
		</item>
		<item>
			<title>How to Set Up a VPS for Beginners, from a Beginner (Part 4: Caddy, HTTPS, and the Private Network)</title>
			<link>https://r3p.dev/blogs/vps-setup-part-4</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/vps-setup-part-4</guid>
			<description>Part 4 of a journey learning how I set up my first VPS on Debian 13 Minimal. Putting Caddy in front as a reverse proxy with automatic HTTPS - a private container network, persistent certificate storage, and a Quadlet unit that survives reboots.</description>
			<pubDate>Sat, 23 May 2026 00:00:00 GMT</pubDate>
			<category>vps</category>
			<category>debian</category>
			<category>caddy</category>
			<category>podman</category>
		</item>
		<item>
			<title>How to Set Up a VPS for Beginners, from a Beginner (Part 3: Rootless Podman and the First Container)</title>
			<link>https://r3p.dev/blogs/vps-setup-part-3</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/vps-setup-part-3</guid>
			<description>Part 3 of a journey learning how I set up my first VPS on Debian 13 Minimal. Getting rootless Podman working properly - the packages Debian Minimal leaves out, UID mapping, lingering, privileged ports, and running a first container as a systemd Quadlet service.</description>
			<pubDate>Fri, 22 May 2026 00:00:00 GMT</pubDate>
			<category>vps</category>
			<category>debian</category>
			<category>podman</category>
			<category>quadlet</category>
		</item>
		<item>
			<title>How to Set Up a VPS for Beginners, from a Beginner (Part 2: Firewall, Bans, and Automatic Updates)</title>
			<link>https://r3p.dev/blogs/vps-setup-part-2</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/vps-setup-part-2</guid>
			<description>Part 2 of a journey learning how I set up my first VPS on Debian 13 Minimal. Fixing the Debian 13 SSH port gotcha, adding swap, closing every port with ufw, banning repeat offenders with fail2ban, and letting security patches apply themselves.</description>
			<pubDate>Thu, 21 May 2026 00:00:00 GMT</pubDate>
			<category>vps</category>
			<category>debian</category>
			<category>server</category>
			<category>security</category>
		</item>
		<item>
			<title>How to Set Up a VPS for Beginners, from a Beginner (Part 1: Securing the Base System)</title>
			<link>https://r3p.dev/blogs/vps-setup-part-1</link>
			<guid isPermaLink="true">https://r3p.dev/blogs/vps-setup-part-1</guid>
			<description>Part 1 of a journey learning how I set up my first VPS on Debian 13 Minimal. This part focuses on initial access, system updates, SSH keys, user separation, and SSH hardening to secure the base system before any services are deployed.</description>
			<pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate>
			<category>vps</category>
			<category>debian</category>
			<category>server</category>
			<category>ssh</category>
		</item>
	</channel>
</rss>
