<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>AI on Ely Clover</title><link>https://dev.elyclover.com/categories/ai/</link><description>Recent content in AI on Ely Clover</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>kevin@elyclover.com (Kevin Holmes)</managingEditor><webMaster>kevin@elyclover.com (Kevin Holmes)</webMaster><copyright>© 2026 Kevin Holmes</copyright><lastBuildDate>Sat, 10 Oct 2026 10:00:00 +0000</lastBuildDate><atom:link href="https://dev.elyclover.com/categories/ai/index.xml" rel="self" type="application/rss+xml"/><item><title>Why I killed my over-engineered portfolio infrastructure</title><link>https://dev.elyclover.com/posts/why-i-killed-my-portfolio-infra/</link><pubDate>Sat, 10 Oct 2026 10:00:00 +0000</pubDate><author>kevin@elyclover.com (Kevin Holmes)</author><guid>https://dev.elyclover.com/posts/why-i-killed-my-portfolio-infra/</guid><description>&lt;blockquote&gt;&lt;p&gt;Outline only. The full write-up is coming.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 class="relative group"&gt;The hook
 &lt;div id="the-hook" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-hook" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A static Hugo site is a folder of files. Mine was spread across 3 repos, 3 cloud environments and a pile of secrets.&lt;/li&gt;
&lt;li&gt;One sentence on where this lands: 2 repos, 1 Pulumi stack, no cloud credentials in CI.&lt;/li&gt;
&lt;li&gt;The thesis, up front: I deleted more than I wrote.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;What I built (and why it was fun)
 &lt;div id="what-i-built-and-why-it-was-fun" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-i-built-and-why-it-was-fun" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The old stack, for context (link back to &lt;a href="https://dev.elyclover.com/posts/how-this-site-runs/" &gt;What powers elyclover.com?&lt;/a&gt;):
&lt;ul&gt;
&lt;li&gt;3 repos: the Hugo site, the Pulumi infra program, and a set of reusable composite Actions&lt;/li&gt;
&lt;li&gt;Azure Storage static hosting behind Azure Classic CDN&lt;/li&gt;
&lt;li&gt;one resource group, storage account, CDN profile and CI service principal per environment (&lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;stg&lt;/code&gt;, &lt;code&gt;prod&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;a prod apex certificate imported as a PFX through Key Vault, because Classic CDN could not issue one for the apex&lt;/li&gt;
&lt;li&gt;SOPS-encrypted secrets and key material kept in Git&lt;/li&gt;
&lt;li&gt;Pulumi pushing service principal credentials into GitHub environments&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;What it taught me: IaC, least-privilege CI auth, GitOps with Release Please.&lt;/li&gt;
&lt;li&gt;Honest take: great learning project, wrong amount of machinery for a bio and resume site.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;The bill that forced the question
 &lt;div id="the-bill-that-forced-the-question" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-bill-that-forced-the-question" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Microsoft retired Azure Classic CDN and pointed everyone at Front Door.&lt;/li&gt;
&lt;li&gt;Hosting cost went from about $1/month to about $75/month.&lt;/li&gt;
&lt;li&gt;The last deploys were already failing, because the CDN endpoints had moved underneath the Pulumi state.&lt;/li&gt;
&lt;li&gt;Decision point: pay for Front Door, fix the Classic setup, or rethink the whole thing.&lt;/li&gt;
&lt;li&gt;Pull out the lesson: a cost spike is a good prompt to ask what the system is for.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;The plan I threw away
 &lt;div id="the-plan-i-threw-away" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-plan-i-threw-away" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;v1 moved the stack to GCP behind Cloudflare, partly as a multi-cloud showcase (&lt;code&gt;MIGRATION_PLAN_v1_gcp.md&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Why it died (my words here): more clouds and more repos to solve a hosting problem that did not need either.&lt;/li&gt;
&lt;li&gt;What replaced it: ask what a static site needs (files, a CDN, TLS, DNS) and let one vendor provide all of it.&lt;/li&gt;
&lt;li&gt;What carried over from v1 (my words here): the habit of writing the plan down first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;What it looks like now (v3)
 &lt;div id="what-it-looks-like-now-v3" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-it-looks-like-now-v3" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare Pages project &lt;code&gt;elyclover&lt;/code&gt;, deployed from GitHub Actions with wrangler Direct Upload.&lt;/li&gt;
&lt;li&gt;Cloudflare-managed TLS: no PFX, no Key Vault, no SOPS.&lt;/li&gt;
&lt;li&gt;2 repos: the site and the Pulumi infra. The reusable Azure Actions repo is retired.&lt;/li&gt;
&lt;li&gt;One Pulumi stack (&lt;code&gt;prod&lt;/code&gt;), written in Go, with tests at 80% coverage or better.&lt;/li&gt;
&lt;li&gt;Environments (&lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;stg&lt;/code&gt;, &lt;code&gt;production&lt;/code&gt;) are Pages branches, not separate clouds.&lt;/li&gt;
&lt;li&gt;No cloud credentials in CI: Pulumi mints a scoped Cloudflare token and writes it into GitHub.&lt;/li&gt;
&lt;li&gt;Release Please flow unchanged: PR goes to &lt;code&gt;dev&lt;/code&gt;, release PR goes to &lt;code&gt;stg&lt;/code&gt;, release goes to the apex.&lt;/li&gt;
&lt;li&gt;Preview environments are &lt;code&gt;noindex&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rough before and after table: repos, stacks, secrets in CI, monthly cost, lines of infra code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;The argument: agents made the writing cheap
 &lt;div id="the-argument-agents-made-the-writing-cheap" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-argument-agents-made-the-writing-cheap" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Agents make writing IaC cheap. Pulumi Go, tests, workflows, docs: the typing is no longer the scarce part.&lt;/li&gt;
&lt;li&gt;So the signal is no longer &amp;ldquo;can you write it&amp;rdquo;. The signal is judgment about what to build.&lt;/li&gt;
&lt;li&gt;Corollary: the cheapest line of infra code is the one you delete, and an agent will happily build the over-engineered version if you ask.&lt;/li&gt;
&lt;li&gt;Example from this migration: the hard part was deciding what to delete, not writing the replacement.&lt;/li&gt;
&lt;li&gt;Counterpoint to address: does cheap code mean sloppy infra? Tests, review gates and human-only steps for anything destructive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;How the migration was run
 &lt;div id="how-the-migration-was-run" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-the-migration-was-run" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The plan was a single document, &lt;code&gt;MIGRATION_PLAN.md&lt;/code&gt;: 29 work packages, each with a dependency, an owner and a verify step.&lt;/li&gt;
&lt;li&gt;One orchestrator session on Opus read the plan, tracked state in a &lt;code&gt;STATE.md&lt;/code&gt; file and launched sub-agents.&lt;/li&gt;
&lt;li&gt;Model split:
&lt;ul&gt;
&lt;li&gt;Haiku: inventory and verification (run the given commands, compare output)&lt;/li&gt;
&lt;li&gt;Sonnet: code, workflows and docs&lt;/li&gt;
&lt;li&gt;Fable: the plan audit and the code and security review&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Guard rails:
&lt;ul&gt;
&lt;li&gt;harness permission rules denied destructive and secret-handling commands for every agent&lt;/li&gt;
&lt;li&gt;agents never saw secret values&lt;/li&gt;
&lt;li&gt;the human did every irreversible step: Azure teardown, token creation, nameserver switch, PR merges&lt;/li&gt;
&lt;li&gt;one agent per repo at a time&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Review before execution: an adversarial review of the plan caught real errors before anything ran.&lt;/li&gt;
&lt;li&gt;Where it went sideways (pick two or three):
&lt;ul&gt;
&lt;li&gt;a few spec errors in the plan itself surfaced during execution and had to be fixed by the human&lt;/li&gt;
&lt;li&gt;a deny rule blocked a read-only check it was meant to allow&lt;/li&gt;
&lt;li&gt;a gate was merged out of order and verification had to run retroactively&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;What worked: reports written to files, so the orchestrator&amp;rsquo;s context stayed small.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;What I would tell someone else
 &lt;div id="what-i-would-tell-someone-else" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-i-would-tell-someone-else" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Start with what the site needs, then pick the platform.&lt;/li&gt;
&lt;li&gt;Write the plan down and have a second model attack it before you execute.&lt;/li&gt;
&lt;li&gt;Keep humans on the irreversible steps.&lt;/li&gt;
&lt;li&gt;Treat deleting infrastructure as a feature.&lt;/li&gt;
&lt;li&gt;Link the repos: &lt;a href="https://github.com/kevholmes/elyclover.com" target="_blank" rel="noreferrer"&gt;site&lt;/a&gt; and &lt;a href="https://github.com/kevholmes/elyclover.com-infra" target="_blank" rel="noreferrer"&gt;infra&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Open items for me before publishing
 &lt;div id="open-items-for-me-before-publishing" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#open-items-for-me-before-publishing" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Replace this outline with prose in my own voice.&lt;/li&gt;
&lt;li&gt;Confirm the cost figures and add the current Cloudflare cost.&lt;/li&gt;
&lt;li&gt;Decide whether to link the v1 GCP plan or only describe it.&lt;/li&gt;
&lt;li&gt;Add one diagram: old stack against new stack.&lt;/li&gt;
&lt;li&gt;Mark the PR ready, merge it, then merge the release PR it creates.&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>