<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Heaviside's Journal]]></title><description><![CDATA[AI, technology, and marketing notes and guides]]></description><link>https://cvsloane.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!-gHA!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5ab7647-8ce6-4681-b867-7282902c29a9_200x200.png</url><title>Heaviside&apos;s Journal</title><link>https://cvsloane.substack.com</link></image><generator>Substack</generator><lastBuildDate>Wed, 08 Apr 2026 09:30:03 GMT</lastBuildDate><atom:link href="https://cvsloane.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Chris Sloane]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[cvsloane@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[cvsloane@substack.com]]></itunes:email><itunes:name><![CDATA[Chris Sloane]]></itunes:name></itunes:owner><itunes:author><![CDATA[Chris Sloane]]></itunes:author><googleplay:owner><![CDATA[cvsloane@substack.com]]></googleplay:owner><googleplay:email><![CDATA[cvsloane@substack.com]]></googleplay:email><googleplay:author><![CDATA[Chris Sloane]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Day 2 Operations]]></title><description><![CDATA[Objective: Master the long-term management of your infrastructure.]]></description><link>https://cvsloane.substack.com/p/day-2-operations</link><guid isPermaLink="false">https://cvsloane.substack.com/p/day-2-operations</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 20:09:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G4ZB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!G4ZB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G4ZB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!G4ZB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!G4ZB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!G4ZB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!G4ZB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:686939,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180054223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!G4ZB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!G4ZB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!G4ZB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!G4ZB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21acd504-ff25-4817-aa50-c8263502ad79_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Master the long-term management of your infrastructure. We cover advanced log analysis using Gemini 1.5 Pro, cost tracking, emergency &#8220;Break Glass&#8221; procedures, and the maintenance rhythm.</p><h2>1. Log Analysis with AI</h2><p>When Vercel throws a 500, you get a graph. When Docker throws a 500, you get a 100MB log file. We use <strong>Gemini</strong> to read it.</p><h3>The Workflow</h3><ol><li><p><strong>Dump</strong>: docker logs --since 1h &lt;container&gt; &gt; crash.log.</p></li><li><p><strong>Analyze</strong>: Pipe to Gemini.</p></li></ol><blockquote><p>&#8220;Analyze this log. Find the stack trace. Correlate with Postgres logs. Explain the root cause.&#8221;</p></blockquote><h2>2. Cost Analysis (The Victory Lap)</h2><p><strong>Scenario</strong>: 500k Users/mo.</p><ul><li><p><strong>Vercel</strong>: ~$550/mo.</p></li><li><p><strong>Self-Hosted</strong>: ~$17.25/mo.</p></li><li><p><strong>Savings</strong>: ~$6,393/yr.</p></li></ul><p>Use this to justify your next hardware upgrade.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HUVB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7067af56-71e0-4819-899f-2b1569c6f862_745x270.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HUVB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7067af56-71e0-4819-899f-2b1569c6f862_745x270.png 424w, https://substackcdn.com/image/fetch/$s_!HUVB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7067af56-71e0-4819-899f-2b1569c6f862_745x270.png 848w, https://substackcdn.com/image/fetch/$s_!HUVB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7067af56-71e0-4819-899f-2b1569c6f862_745x270.png 1272w, https://substackcdn.com/image/fetch/$s_!HUVB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7067af56-71e0-4819-899f-2b1569c6f862_745x270.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HUVB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7067af56-71e0-4819-899f-2b1569c6f862_745x270.png" width="745" height="270" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7067af56-71e0-4819-899f-2b1569c6f862_745x270.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:270,&quot;width&quot;:745,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46735,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180054223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7067af56-71e0-4819-899f-2b1569c6f862_745x270.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HUVB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7067af56-71e0-4819-899f-2b1569c6f862_745x270.png 424w, https://substackcdn.com/image/fetch/$s_!HUVB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7067af56-71e0-4819-899f-2b1569c6f862_745x270.png 848w, https://substackcdn.com/image/fetch/$s_!HUVB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7067af56-71e0-4819-899f-2b1569c6f862_745x270.png 1272w, https://substackcdn.com/image/fetch/$s_!HUVB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7067af56-71e0-4819-899f-2b1569c6f862_745x270.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>3. Emergency Procedures (&#8221;Break Glass&#8221;)</h2><h3>A. Disk Full</h3><p><strong>Symptom</strong>: DB stops writing. <strong>Fix</strong>:</p><ol><li><p>ncdu /.</p></li><li><p>Truncate Docker logs: truncate -s 0 ....</p></li><li><p>Prune images: docker system prune.</p></li></ol><h3>B. 502 Bad Gateway</h3><p><strong>Symptom</strong>: Site down. <strong>Fix</strong>:</p><ol><li><p>Check Traefik logs.</p></li><li><p>Restart App container.</p></li><li><p>Check OOM: dmesg | grep killed.</p></li></ol><h2>4. TLS Expiry Monitoring</h2><p>Cloudflare handles the Edge certificate, but in <strong>Full (Strict)</strong> mode, it validates your <strong>Origin Certificate</strong> (managed by Traefik on apps-vps). If Traefik fails to renew, your site goes down (Error 526).</p><p><strong>We monitor the Origin Cert directly.</strong></p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Create a script /opt/monitoring/scripts/check_coolify_origin_cert.sh.</p></blockquote><ol><li><p>It should connect to localhost:443 (Traefik).</p></li><li><p>Extract the expiration date of the certificate.</p></li><li><p>If expiry &lt; 7 days, send an alert to Alertmanager/SES. Add this to the daily cron.&#8221;</p></li></ol><h2>5. The Maintenance Rhythm</h2><ul><li><p><strong>Daily</strong>: Check Backup emails.</p></li><li><p><strong>Monthly</strong>: Restore Drill (Automated).</p></li><li><p><strong>Quarterly</strong>: OS Updates (apt upgrade). Reboot.</p></li></ul><h2>6. Conclusion</h2><p>You are no longer a tenant. You are the landlord. You own the metal. You own the data. <strong>You are the Architect.</strong></p>]]></content:encoded></item><item><title><![CDATA[The Cutover Event]]></title><description><![CDATA[Objective: Execute the Go-Live sequence.]]></description><link>https://cvsloane.substack.com/p/the-cutover-event</link><guid isPermaLink="false">https://cvsloane.substack.com/p/the-cutover-event</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 20:06:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!awci!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!awci!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!awci!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!awci!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!awci!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!awci!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!awci!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:632284,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180054088?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!awci!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!awci!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!awci!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!awci!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e69e00f-f8cc-45c6-9487-7b34b71c43be_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Execute the Go-Live sequence. We will perform a zero-downtime DNS switch using Cloudflare, ensure email deliverability with DKIM/SPF/DMARC, and verify SSL propagation.</p><h2>1. The Pre-Flight Checklist (T-Minus 24 Hours)</h2><ol><li><p><strong>TTL</strong>: Lower DNS TTL to 60s.</p></li><li><p><strong>Freeze</strong>: Code freeze on Vercel.</p></li><li><p><strong>Dry Run</strong>: Run the import script on db-vps (delete test data first).</p></li></ol><h2>2. The Cutover Sequence (T-Minus 0)</h2><h3>Step 1: Maintenance Mode</h3><p>We stop writes to the old DB. <strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Create a maintenance page. Configure Vercel to redirect all traffic to it (or break the DB connection).&#8221;</p></blockquote><h3>Step 2: The Migration</h3><ol><li><p><strong>Export</strong>: Run export.sh.</p></li><li><p><strong>Transfer</strong>: scp to db-vps.</p></li><li><p><strong>Import</strong>: Run import.sh.</p></li><li><p><strong>Verify</strong>: Check row counts.</p></li></ol><h3>Step 3: The Switch</h3><p><strong>Agent</strong>: Claude Code (Local) <strong>Prompt</strong>:</p><blockquote><p>&#8220;Create a script switch_dns.sh using curl against Cloudflare API. Update the A record for @ to the Apps VPS IP (144.126.x.x). Ensure proxied: true.&#8221;</p></blockquote><h3>Step 4: Validation</h3><p>Check https://app.yourdomain.com. Test Login. Test DB write.</p><h2>3. Email Deliverability (DKIM/SPF)</h2><p>Your app sends emails. If you don&#8217;t fix DNS, they go to Spam.</p><h3>DKIM</h3><p>AWS gives you 3 CNAMEs. <strong>Crucial</strong>: These must be <strong>DNS Only</strong> (Grey Cloud) in Cloudflare. If proxied, validation fails.</p><h3>SPF &amp; DMARC</h3><p><strong>Prompt</strong>:</p><blockquote><p>&#8220;Create a script setup_email_dns.sh using curl to add:</p></blockquote><ol><li><p>SPF TXT: v=spf1 include:amazonses.com ~all.</p></li><li><p>DMARC TXT: v=DMARC1; p=none; rua=mailto:admin@.... Ensure proxied: false.&#8221;</p></li></ol><h2>4. Context Update</h2><p><strong>Agent</strong>: Codex <strong>Prompt</strong>:</p><blockquote><p>&#8220;System is LIVE. DNS points to VPS. Email authenticated. Update INFRASTRUCTURE_CONTEXT.md.&#8221;</p></blockquote><div><hr></div><p><strong>Next Step</strong>: You are live. Now you live with it. Go to <a href="https://open.substack.com/pub/cvsloane/p/day-2-operations?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">14. Day 2 Operations</a>.</p>]]></content:encoded></item><item><title><![CDATA[Production Configuration]]></title><description><![CDATA[Objective: Configure the application environment.]]></description><link>https://cvsloane.substack.com/p/production-configuration</link><guid isPermaLink="false">https://cvsloane.substack.com/p/production-configuration</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 20:04:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JZcV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JZcV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JZcV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!JZcV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!JZcV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!JZcV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JZcV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:518918,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180053946?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JZcV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!JZcV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!JZcV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!JZcV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82dce677-7eee-4a4a-bb32-c2aa99cd3209_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Configure the application environment. We will securely map secrets in Coolify, update OAuth providers to trust our new domain, and deploy persistent background workers to handle long-running tasks.</p><h2>1. Secrets Management with Bitwarden (BWS)</h2><p>Stop pasting .env files into web forms. We use <strong>Bitwarden Secrets Manager (bws)</strong> to maintain a single source of truth for secrets.</p><h3>The Workflow</h3><ol><li><p><strong>Local</strong>: You update secrets in Bitwarden.</p></li><li><p><strong>Inject</strong>: You use bws to generate the env block for Coolify.</p></li></ol><p><strong>Agent</strong>: Claude Code (Local) <strong>Prompt</strong>:</p><blockquote><p>&#8220;I need to generate the environment variables for Coolify using bws.</p></blockquote><ol><li><p>List secrets in project &#8216;My App&#8217;: bws secret list --project-id &lt;ID&gt;.</p></li><li><p>Format them as KEY=VALUE.</p></li><li><p>Replace DATABASE_URL with the production internal URL (PgBouncer).</p></li><li><p>Output the block so I can paste it into Coolify (or script the API call if Coolify API is available).&#8221;</p></li></ol><p><em>Note: We also maintain a custom tool bws-init (see your 20 - Development/21 - Projects/Bws Init.md) to initialize this structure.</em></p><h3>The Map</h3><ul><li><p>DATABASE_URL: postgres://user:pass@100.x.y.z:6432/db (Use Tailscale IP + PgBouncer Port).</p></li><li><p>NEXTAUTH_URL: https://app.yourdomain.com (Must match exactly).</p></li><li><p>NEXTAUTH_SECRET: Keep the old one to preserve user sessions.</p></li></ul><h2>2. Fixing OAuth (The &#8220;Login Failed&#8221; Error)</h2><p>Google and GitHub only trust the domains you whitelist. <strong>Action</strong>:</p><ol><li><p>Go to Google Cloud Console -&gt; Credentials.</p></li><li><p>Add https://app.yourdomain.com/api/auth/callback/google.</p></li><li><p>Repeat for GitHub.</p></li></ol><h2>3. Background Workers (The Superpower)</h2><p>Vercel limits functions to 60s. We can now run tasks for hours.</p><h3>Redis</h3><p>Deploy Redis on apps-vps via Coolify (Internal access only).</p><h3>The Worker Service</h3><ol><li><p>Clone your Web App in Coolify.</p></li><li><p>Change Start Command: npm run worker.</p></li><li><p>This runs the <em>same code</em> but starts the queue processor instead of the web server.</p></li></ol><h3>Dashboard</h3><p>Mount Bull Board at /admin/queues. <strong>Security</strong>: Protect this route with Middleware!</p><h2>4. Context Update</h2><p><strong>Agent</strong>: Codex <strong>Prompt</strong>:</p><blockquote><p>&#8220;Production Config Complete. Secrets managed via bws. OAuth updated. Workers active. Update INFRASTRUCTURE_CONTEXT.md.&#8221;</p></blockquote><div><hr></div><p><strong>Next Step</strong>: The system is built. The data is migrated. It is time to flip the switch. Go to <a href="https://open.substack.com/pub/cvsloane/p/the-cutover-event?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">13. The Cutover Event</a>.</p>]]></content:encoded></item><item><title><![CDATA[The Great Data Migration]]></title><description><![CDATA[Objective: Extract your entire database from Supabase (including Auth users), sanitize it, and hydrate your new Postgres instance.]]></description><link>https://cvsloane.substack.com/p/the-great-data-migration</link><guid isPermaLink="false">https://cvsloane.substack.com/p/the-great-data-migration</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 20:02:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!s89H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s89H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s89H!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!s89H!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!s89H!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!s89H!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s89H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:597470,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180053789?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!s89H!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!s89H!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!s89H!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!s89H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F580c1467-4b28-4880-b087-1c648732f088_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1></h1><p><strong>Objective</strong>: Extract your entire database from Supabase (including Auth users), sanitize it, and hydrate your new Postgres instance. This is the most dangerous phase; we will script it meticulously.</p><h2>1. The Export Strategy</h2><p>Supabase locks down superuser access. pg_dumpall fails. We need a surgical extraction.</p><h3>The Export Script (export.sh)</h3><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Write export.sh accepting SUPABASE_URL.</p></blockquote><ol><li><p>Dump auth schema (Schema + Data).</p></li><li><p>Dump public schema (Schema + Data).</p></li><li><p><strong>Exclude</strong>: storage, realtime, graphql (Supabase internals).</p></li><li><p>Use --clean --if-exists flags.&#8221;</p></li></ol><h2>2. The Import Logic</h2><h3>A. Roles</h3><p>Supabase uses anon, authenticated, service_role. We must create these in our new DB, or the import will fail.</p><p><strong>Prompt</strong>:</p><blockquote><p>&#8220;Create setup_roles.sql. Create roles if not exist. Grant usage on public schema.&#8221;</p></blockquote><h3>B. Extensions</h3><p>Supabase has 50 extensions. We only need uuid-ossp and pgcrypto. We install these manually in the import script.</p><h2>3. The Sequence Trap</h2><p><strong>Critical</strong>: When you insert data with explicit IDs, Postgres sequences <strong>do not update</strong>. Next insert -&gt; Duplicate Key Error.</p><p><strong>Prompt</strong>:</p><blockquote><p>&#8220;Write a SQL snippet to reset all sequences in public schema to MAX(id).&#8221;</p></blockquote><h2>4. The Master Import Script (import.sh)</h2><p><strong>Prompt</strong>:</p><blockquote><p>&#8220;Combine everything into import.sh on db-vps.</p></blockquote><ol><li><p>Create Roles.</p></li><li><p>Create Extensions.</p></li><li><p>Import Auth.</p></li><li><p>Import Public.</p></li><li><p>Reset Sequences.</p></li><li><p>Run ANALYZE.&#8221;</p></li></ol><h2>5. Verification</h2><p><strong>Prompt</strong>:</p><blockquote><p>&#8220;Run import. Compare SELECT count(*) FROM auth.users between Supabase and Local.&#8221;</p></blockquote><div><hr></div><p><strong>Next Step</strong>: Data is live. App is ready. Let&#8217;s wire them together. Go to <a href="https://open.substack.com/pub/cvsloane/p/production-configuration?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">12. Production Configuration</a>.</p>]]></content:encoded></item><item><title><![CDATA[Containerizing Next.js]]></title><description><![CDATA[Objective: Transform your Vercel-optimized Next.js application into a lean, mean Docker container.]]></description><link>https://cvsloane.substack.com/p/containerizing-nextjs</link><guid isPermaLink="false">https://cvsloane.substack.com/p/containerizing-nextjs</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 20:00:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zyZN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zyZN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zyZN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zyZN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zyZN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zyZN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zyZN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:671112,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180053399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zyZN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zyZN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zyZN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zyZN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec5affd9-4a6c-47b5-83c7-63571c382927_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Transform your Vercel-optimized Next.js application into a lean, mean Docker container. We will use Multi-Stage builds and output: standalone to shrink the image size from 1GB+ to &lt;100MB, ensuring fast deployments and minimal attack surface.</p><h2>1. The &#8220;Standalone&#8221; Magic</h2><p>Vercel&#8217;s platform automatically traces your code dependencies. When self-hosting, we must replicate this. Next.js has a feature called <strong>Output Standalone</strong>. It creates a .next/standalone folder containing a minimal server.js and <em>only</em> the necessary node_modules.</p><h3>Configuration</h3><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Edit next.config.mjs. Add output: &#8216;standalone&#8217;. Explain why this reduces image size.&#8221;</p></blockquote><h2>2. The Multi-Stage Dockerfile</h2><p>We don&#8217;t want our production image to contain TypeScript compilers, Webpack, or dev dependencies. We use a <strong>3-Stage Build</strong>:</p><ol><li><p><strong>Deps</strong>: Install dependencies (cached layer).</p></li><li><p><strong>Builder</strong>: Copy source, run npm run build.</p></li><li><p><strong>Runner</strong>: Copy only .next/standalone, public, and .next/static.</p></li></ol><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Create a production Dockerfile. Base: node:20-alpine. User: nextjs (Non-root security). Env: NEXT_TELEMETRY_DISABLED=1. Expose: 3000. Command: node server.js.&#8221;</p></blockquote><h2>3. The Build-Time vs. Run-Time Trap</h2><p><strong>NEXT_PUBLIC_ Variables</strong>: These are inlined into the JavaScript bundle at <strong>Build Time</strong>. If you build your image on GitHub Actions, NEXT_PUBLIC_API_URL is baked in. <strong>Solution</strong>:</p><ul><li><p>Either build the image <em>on</em> the server (Coolify does this).</p></li><li><p>Or use &#8220;Runtime Configuration&#8221; patterns (advanced). For this course, we rely on Coolify building the image, so secrets are injected during the build.</p></li></ul><h2>4. Local Verification</h2><p><strong>Prompt</strong>:</p><blockquote><p>&#8220;Build locally: docker build -t my-app . Run: docker run -p 3000:3000 my-app Verify size: docker images | grep my-app.&#8221;</p></blockquote><div><hr></div><p><strong>Next Step</strong>: Code is ready. Data is trapped in Supabase. Go to <a href="https://open.substack.com/pub/cvsloane/p/the-great-data-migration?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">11. The Great Data Migration</a>.</p>]]></content:encoded></item><item><title><![CDATA[Data Durability Strategy]]></title><description><![CDATA[Objective: Implement a &#8220;Paranoid&#8221; backup strategy.]]></description><link>https://cvsloane.substack.com/p/data-durability-strategy</link><guid isPermaLink="false">https://cvsloane.substack.com/p/data-durability-strategy</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 19:51:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!jOsi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jOsi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jOsi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!jOsi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!jOsi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!jOsi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jOsi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:543110,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180053008?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jOsi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!jOsi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!jOsi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!jOsi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263b55e0-1a22-43bf-b7ac-324a1c3c95d1_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Implement a &#8220;Paranoid&#8221; backup strategy. We will script automated nightly dumps to <strong>Cloudflare R2</strong> (S3 Compatible) and, crucially, create an automated <strong>&#8220;Restore Drill&#8221;</strong> that verifies the backup integrity every month.</p><h2>1. The Hybrid Backup Strategy</h2><p>We do not rely on a single backup method. We use a <strong>Hybrid Strategy</strong>:</p><ol><li><p><strong>WAL-G (Primary)</strong>: Continuously streams Write Ahead Logs (WAL) to Cloudflare R2.</p><ul><li><p><strong>Benefit</strong>: Point-in-Time Recovery (PITR). Can restore to <em>any second</em>.</p></li><li><p><strong>Speed</strong>: Extremely fast binary restore.</p></li></ul></li><li><p><strong>Logical Dumps (Secondary)</strong>: Nightly pg_dumpall to local disk (retained 7 days).</p><ul><li><p><strong>Benefit</strong>: Portable. Human-readable SQL. Can restore to different Postgres versions.</p></li><li><p><strong>Safety</strong>: If R2 credentials fail, we have local copies.</p></li></ul></li></ol><h2>2. R2 Integration</h2><p>Cloudflare R2 has zero egress fees. It is perfect for both WAL archives and dump storage.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Create R2 bucket postgres-backups via Cloudflare Dashboard or API (curl). Install aws-cli and wal-g binary on db-vps. Configure wal-g environment variables (WALG_S3_PREFIX, AWS_ACCESS_KEY_ID, etc.) pointing to the R2 bucket.&#8221;</p></blockquote><h2>3. The Backup Script (backup.sh) - Logical</h2><p>We place this in /opt/postgres/scripts/backup.sh.</p><p><strong>Features</strong>:</p><ol><li><p><strong>Dump</strong>: docker exec ... pg_dumpall | gzip.</p></li><li><p><strong>Upload</strong>: (Optional) Upload to R2 (separate prefix) or keep local.</p></li><li><p><strong>Retention</strong>: Delete local &gt; 7 days.</p></li><li><p><strong>Logging</strong>: Write to /var/log/postgres_backup.log.</p></li></ol><h2>4. The Restore Drill (restore_drill.sh)</h2><p>This is the most important script you will ever write. It runs on the 1st of the month via cron (0 4 1 * *).</p><p><strong>Workflow (WAL-G Validation)</strong>:</p><ol><li><p>Use wal-g backup-fetch to download the latest base backup to a scratch directory (/tmp/walg-restore-test).</p></li><li><p>Run pg_controldata on the fetched data directory to validate the backup integrity.</p></li><li><p>Check that Database cluster state shows a valid state.</p></li><li><p>Log results to /var/log/postgres-restore-drill.log.</p></li><li><p>Clean up scratch directory (use --keep flag to preserve for deeper inspection).</p></li></ol><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Create /opt/postgres/scripts/restore_drill.sh:</p><p>#!/bin/bash</p><p>set -euo pipefail</p><p>LOG=/var/log/postgres-restore-drill.log</p><p>SCRATCH=/tmp/walg-restore-test</p><p>KEEP=false</p><p>[[ &#8220;${1:-}&#8221; == &#8220;--keep&#8221; ]] &amp;&amp; KEEP=true</p><p>echo &#8220;$(date): Starting restore drill&#8221; &gt;&gt; $LOG</p><p>rm -rf $SCRATCH &amp;&amp; mkdir -p $SCRATCH</p><p># Fetch latest backup</p><p>docker exec postgres_db wal-g backup-fetch $SCRATCH LATEST 2&gt;&gt;$LOG</p><p># Validate with pg_controldata</p><p>RESULT=$(docker exec postgres_db pg_controldata $SCRATCH 2&gt;&gt;$LOG | grep &#8216;Database cluster state&#8217; || echo &#8216;FAILED&#8217;)</p><p>echo &#8220;$(date): $RESULT&#8221; &gt;&gt; $LOG</p><p>if [[ &#8220;$KEEP&#8221; == &#8220;false&#8221; ]]; then</p><p>  rm -rf $SCRATCH</p><p>  echo &#8220;$(date): Cleanup complete&#8221; &gt;&gt; $LOG</p><p>fi</p></blockquote><ol><li><p>Make executable: chmod +x /opt/postgres/scripts/restore_drill.sh</p></li><li><p>Add monthly cron: 0 4 1 * * root /opt/postgres/scripts/restore_drill.sh &gt;&gt; /var/log/postgres-restore-drill-cron.log 2&gt;&amp;1</p></li><li><p>Test manually: /opt/postgres/scripts/restore_drill.sh --keep to verify.&#8221;</p></li></ol><p><em>For deeper validation, use --keep and spin up a temporary Postgres container on a different port to run actual queries against the restored data.</em></p><h2>5. Restore Drill Monitoring</h2><p>A backup that isn&#8217;t tested is worthless. We need alerts if the drill stops running.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Create /opt/postgres/scripts/restore_drill_monitor.sh:</p><p>#!/bin/bash</p><p>LOG=/var/log/postgres-restore-drill.log</p><p>WARN_DAYS=35</p><p>if [[ ! -f $LOG ]]; then</p><p>  echo &#8220;No restore drill log found!&#8221;</p><p>  exit 1</p><p>fi</p><p>LAST_RUN=$(stat -c %Y $LOG)</p><p>NOW=$(date +%s)</p><p>DAYS_AGO=$(( ($NOW - $LAST_RUN) / 86400 ))</p><p>if [[ $DAYS_AGO -gt $WARN_DAYS ]]; then</p><p>  # Post alert to Alertmanager</p><p>  curl -s -X POST http://localhost:9093/api/v2/alerts \</p><p>    -H &#8216;Content-Type: application/json&#8217; \</p><p>    -d &#8216;[{&#8221;labels&#8221;:{&#8221;alertname&#8221;:&#8221;RestoreDrillStale&#8221;,&#8221;severity&#8221;:&#8221;warning&#8221;},&#8221;annotations&#8221;:{&#8221;summary&#8221;:&#8221;Restore drill not run in &#8216;$DAYS_AGO&#8217; days&#8221;}}]&#8217;</p><p>fi</p></blockquote><ol><li><p>Add daily cron: /etc/cron.d/restore_drill_monitor</p></li><li><p>This fires a RestoreDrillStale alert if no drill has run in 35+ days.&#8221;</p></li></ol><h2>6. Context Update</h2><p><strong>Agent</strong>: Codex <strong>Prompt</strong>:</p><blockquote><p>&#8220;Backups &amp; Drills Configured. Target: Cloudflare R2. Schedule: Daily logical backup (0 2 * * *), Monthly restore drill (0 4 1 * *). Monitoring: RestoreDrillStale alert fires if drill is &gt;35 days old. Update INFRASTRUCTURE_CONTEXT.md.&#8221;</p></blockquote><div><hr></div><p><strong>Next Step</strong>: The infrastructure is complete. Time to move the Application. Go to <a href="https://open.substack.com/pub/cvsloane/p/containerizing-nextjs?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">10. Containerizing Next.js</a>.</p>]]></content:encoded></item><item><title><![CDATA[High-Performance Database]]></title><description><![CDATA[Objective: Architect the dedicated db-vps.]]></description><link>https://cvsloane.substack.com/p/high-performance-database</link><guid isPermaLink="false">https://cvsloane.substack.com/p/high-performance-database</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 19:48:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!eGcY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eGcY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eGcY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!eGcY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!eGcY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!eGcY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eGcY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:747020,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180052784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!eGcY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!eGcY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!eGcY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!eGcY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa836ba02-30d8-44a2-b1f5-8bbb7e7fd0dc_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Architect the dedicated db-vps. We will deploy PostgreSQL 17 via Docker Compose, tune it for our specific hardware using AI calculations, and implement <strong>PgBouncer</strong> to solve the &#8220;Thundering Herd&#8221; problem of serverless connections.</p><p><em>Note: PostgreSQL 17 (released September 2024) brings improvements including incremental backup support, enhanced COPY performance, and improved JSON support.</em></p><h2>1. The &#8220;Pet&#8221; Philosophy</h2><p>We treat db-vps differently.</p><ul><li><p><strong>No Auto-Updates</strong>: We pin Docker image versions (postgres:17-alpine).</p></li><li><p><strong>No Abstractions</strong>: We use raw docker-compose.yml.</p></li><li><p><strong>Manual Tuning</strong>: We edit postgresql.conf.</p></li></ul><h2>2. The Docker Stack</h2><h3>Directory Structure</h3><p>/opt/postgres </p><p>&#9500;&#9472;&#9472; data/ (Volume) </p><p>&#9500;&#9472;&#9472; config/ (postgresql.conf, pg_hba.conf, pgbouncer.ini) </p><p>&#9492;&#9472;&#9472; docker-compose.yml</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Create this structure on db-vps. Write docker-compose.yml: Services: postgres (17-alpine), pgbouncer (edoburu/pgbouncer). <strong>Critical</strong>: Set shm_size: 1gb for Postgres (default 64m crashes). Network: Bind to 127.0.0.1 or specific Tailscale IP.&#8221;</p></blockquote><h2>3. AI-Driven Configuration Tuning</h2><p>Default Postgres is tuned for 512MB RAM. We have 8GB.</p><p><strong>Agent</strong>: Gemini CLI <strong>Prompt</strong>:</p><blockquote><p>&#8220;I have 8GB RAM, 6 vCPU. Workload: Web App (Read Heavy). Generate postgresql.conf. Calculate:</p></blockquote><ul><li><p>shared_buffers (2GB)</p></li><li><p>effective_cache_size (6GB)</p></li><li><p>maintenance_work_mem (512MB)</p></li><li><p>work_mem (16MB)</p></li><li><p>max_connections (100 - Let PgBouncer handle the rest). Provide the file content.&#8221;</p></li></ul><h2>4. PgBouncer: The Connection Multiplexer</h2><p>Next.js Serverless functions open a new TCP connection for every request. 1000 requests = 1000 Connections = <strong>Crash</strong>.</p><p>PgBouncer maintains a pool of connections to Postgres. It shares them among clients.</p><h3>Configuration (pgbouncer.ini)</h3><ul><li><p><strong>Mode</strong>: transaction. (Client keeps connection only for query duration).</p></li><li><p><strong>Max Client Conn</strong>: <strong>400</strong>. (Allows high concurrency from Next.js).</p></li><li><p><strong>Default Pool Size</strong>: <strong>40</strong>. (Enough real connections for 6 vCPU).</p></li></ul><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Generate pgbouncer.ini and userlist.txt. Use pool_mode = transaction. Set max_client_conn = 400. Set default_pool_size = 40. Set listen_port = 6543. Verify connection from apps-vps using psql -h db-vps -p 6543.&#8221;</p></blockquote><h2>5. Context Update</h2><p><strong>Agent</strong>: Codex <strong>Prompt</strong>:</p><blockquote><p>&#8220;Database Active. PostgreSQL 17 Tuned (direct connections on port 5432). PgBouncer listening on Port 6543 (Tailscale) for pooled connections. Update INFRASTRUCTURE_CONTEXT.md.&#8221;</p></blockquote><p><em>Note: Port 6543 is used to avoid conflicts with any local PostgreSQL installations that might use the default 6432.</em></p><div><hr></div><p><strong>Next Step</strong>: The DB is fast. Is it safe? Go to <a href="https://open.substack.com/pub/cvsloane/p/data-durability-strategy?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">09. Data Durability Strategy</a>.</p>]]></content:encoded></item><item><title><![CDATA[The Edge Layer]]></title><description><![CDATA[Objective: Configure Cloudflare as our global shield using curl and the Cloudflare API.]]></description><link>https://cvsloane.substack.com/p/the-edge-layer</link><guid isPermaLink="false">https://cvsloane.substack.com/p/the-edge-layer</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 19:45:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zgn0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zgn0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zgn0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zgn0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zgn0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zgn0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zgn0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/befc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:627203,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180052536?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zgn0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zgn0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zgn0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zgn0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbefc92f6-a020-49d4-ba0e-0117e6ede497_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Configure <strong>Cloudflare</strong> as our global shield using curl and the Cloudflare API. We will implement &#8220;Full (Strict)&#8221; SSL, configure HSTS, and verify that Coolify&#8217;s internal Traefik proxy correctly handles certificate generation behind Cloudflare&#8217;s proxy.</p><h2>1. The Edge Strategy</h2><p>We are not exposing our server directly. We are putting Cloudflare in front.</p><ul><li><p><strong>DDoS Protection</strong>: Cloudflare absorbs attacks. Our IP is hidden.</p></li><li><p><strong>Performance</strong>: CDN caches static assets at the edge.</p></li><li><p><strong>SSL Offloading</strong>: Cloudflare handles the heavy crypto.</p></li></ul><p><strong>The Tool</strong>: Cloudflare API (via curl). We don&#8217;t click buttons in the UI. We write Infrastructure as Code using shell scripts.</p><h2>2. DNS Configuration via API</h2><p>We will manage our DNS records programmatically using the Cloudflare API. This allows us to version control our infrastructure.</p><h3>Step 1: The Setup Script</h3><p>Create a script setup_dns.sh locally. You will need your <strong>Zone ID</strong> and <strong>API Token</strong>.</p><p><strong>Agent</strong>: Claude Code (Local) <strong>Prompt</strong>:</p><blockquote><p>&#8220;Create a bash script setup_dns.sh to add Cloudflare DNS records using curl. <strong>Variables</strong>: ZONE_ID, TOKEN, IP=144.126.x.x, TAILSCALE_IP=100.x.y.z. <strong>Records to Create</strong>:</p></blockquote><ol><li><p><strong>Root (@)</strong>: A record to $IP, proxied (true).</p></li><li><p><strong>Wildcard (*)</strong>: CNAME to @, proxied (true).</p></li><li><p><strong>Coolify (coolify)</strong>: A record to $TAILSCALE_IP, proxied (false) - DNS Only.</p></li></ol><blockquote><p>The script should use: curl -X POST &#8220;https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records&#8221; ... &#8220;</p></blockquote><h3>Step 2: Execution</h3><p>Run the script locally: export TOKEN=... ZONE_ID=...; bash setup_dns.sh</p><h2>3. SSL Architecture: &#8220;Full (Strict)&#8221;</h2><p>This is the most misunderstood setting.</p><ul><li><p><strong>Flexible</strong>: Encrypted to Cloudflare, Unencrypted to Server. <strong>BAD</strong>.</p></li><li><p><strong>Full</strong>: Encrypted to Server, but accepts Self-Signed certs. <strong>OKAY</strong>.</p></li><li><p><strong>Full (Strict)</strong>: Encrypted to Server, requires Valid Trusted Cert. <strong>BEST</strong>.</p></li></ul><p><strong>We choose Full (Strict).</strong> This means our apps-vps MUST have a valid Let&#8217;s Encrypt certificate.</p><h3>Traefik &amp; Let&#8217;s Encrypt</h3><p>Coolify uses Traefik. Traefik requests certs. <strong>The Magic</strong>: Cloudflare is smart enough to pass /.well-known/acme-challenge requests through to the origin, even when proxied.</p><h2>4. Deployment Verification</h2><p>Let&#8217;s prove it works.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Deploy a &#8216;Hello World&#8217; Next.js app in Coolify. Domain: test.app.com. <strong>Verify</strong>:</p></blockquote><ol><li><p>curl -I https://test.app.com. Check for cf-cache-status.</p></li><li><p>Check SSL issuer: openssl s_client .... Should be Let&#8217;s Encrypt (on the backend) or Cloudflare (on the frontend).</p></li><li><p>Verify HTTP-&gt;HTTPS redirect.&#8221;</p></li></ol><h2>5. HSTS &amp; Security Headers</h2><p><strong>HSTS (HTTP Strict Transport Security)</strong> tells browsers: &#8220;Never talk to me via HTTP again.&#8221; Enable this in Cloudflare Dashboard -&gt; SSL/TLS -&gt; Edge Certificates. <em>Warning</em>: Once enabled, you cannot downgrade to HTTP for months.</p><h2>6. TLS Certificate Monitoring</h2><p>You now have <strong>two layers of certificates</strong> to monitor:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t221!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t221!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png 424w, https://substackcdn.com/image/fetch/$s_!t221!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png 848w, https://substackcdn.com/image/fetch/$s_!t221!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png 1272w, https://substackcdn.com/image/fetch/$s_!t221!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t221!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png" width="973" height="146" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:146,&quot;width&quot;:973,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31293,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180052536?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!t221!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png 424w, https://substackcdn.com/image/fetch/$s_!t221!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png 848w, https://substackcdn.com/image/fetch/$s_!t221!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png 1272w, https://substackcdn.com/image/fetch/$s_!t221!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51e4351a-1c24-4a27-b535-f45079a50d55_973x146.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;We need to monitor both certificate layers.</p></blockquote><ol><li><p><strong>Edge (Cloudflare)</strong>: Use the TLS expiry script from <a href="http://06_observability_stack.md">Module 06</a> pointing at your public domain.</p></li><li><p><strong>Origin (Traefik)</strong>: Create check_coolify_origin_cert.sh to inspect Traefik&#8217;s cert files or connect directly to http://localhost:8000 (bypassing Cloudflare).</p></li><li><p>Add to cron alongside the edge check. Why both? Cloudflare can mask origin cert failures until you disable proxy mode.&#8221;</p></li></ol><p><em>Reference: See <a href="http://06_observability_stack.md#5-tls-certificate-monitoring">06. Observability Stack</a> for the full monitoring script.</em></p><h2>7. Context Update</h2><p><strong>Agent</strong>: Codex <strong>Prompt</strong>:</p><blockquote><p>&#8220;Edge Layer Active. DNS: Managed via Cloudflare API scripts. SSL: Full (Strict). Security: Break-glass account created, 2FA active. Update INFRASTRUCTURE_CONTEXT.md.&#8221;</p></blockquote><div><hr></div><p><strong>Next Step</strong>: The App Server is ready. Now for the heavy lifting. Go to <a href="https://open.substack.com/pub/cvsloane/p/high-performance-database?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">08. High-Performance Database</a>.</p>]]></content:encoded></item><item><title><![CDATA[Observability Stack]]></title><description><![CDATA[Objective: Deploy a &#8220;Survival&#8221; monitoring stack.]]></description><link>https://cvsloane.substack.com/p/observability-stack</link><guid isPermaLink="false">https://cvsloane.substack.com/p/observability-stack</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 19:41:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!EnOA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EnOA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EnOA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!EnOA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!EnOA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!EnOA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EnOA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:594897,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180052275?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EnOA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!EnOA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!EnOA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!EnOA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb46ab9-8e9c-435e-be88-504709ec6215_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Deploy a &#8220;Survival&#8221; monitoring stack. If Coolify crashes, we need to know why. Therefore, we run monitoring <em>outside</em> of Coolify using standard Docker Compose. We will implement Prometheus, Node Exporter, Uptime Kuma, and Alertmanager.</p><h2>1. The &#8220;Separate Stack&#8221; Philosophy</h2><p>Why not run Monitoring inside Coolify?</p><ul><li><p><strong>Circular Dependency</strong>: If Coolify&#8217;s database fails, Coolify goes down. If Coolify manages monitoring, monitoring goes down. You are flying blind.</p></li><li><p><strong>Resources</strong>: Monitoring needs guaranteed CPU. We don&#8217;t want a Next.js memory leak to kill Prometheus.</p></li></ul><h2>2. The Stack Architecture</h2><p>We deploy to /opt/monitoring on apps-vps. <em>Note: While we run this on the same <strong>Host</strong> (Apps VPS) for cost efficiency, it runs as a completely separate Docker Compose stack from Coolify. If Coolify&#8217;s Docker network implodes, this stack (on the host network or its own bridge) survives.</em></p><ul><li><p><strong>Prometheus</strong>: The brain. Scrapes metrics every 15s.</p></li><li><p><strong>Node Exporter</strong>: The sensor. Reports CPU, RAM, Disk, Network.</p></li><li><p><strong>Uptime Kuma</strong>: The dashboard. Pretty graphs for &#8220;Is Google Up?&#8221;</p></li><li><p><strong>Alertmanager</strong>: The dispatcher. Sends emails via SES.</p></li><li><p><strong>Custom Scripts</strong>: We add bash scripts to monitor TLS expiry and Backup freshness.</p></li><li><p><strong>UptimeRobot (External)</strong>: Third-party HTTP(S) checks every 60s with email alerts (contact: <a href="mailto:chris@heavisidegroup.com">chris@heavisidegroup.com</a>). Managed via API, not stored in Coolify.</p></li></ul><h2>3. Deployment via AI</h2><h3>Step 1: Directory Setup</h3><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;SSH into apps-vps. Create /opt/monitoring. Create subdirs prometheus, alertmanager, uptime-kuma-data, scripts. Chown to deploy user.&#8221;</p></blockquote><h3>Step 2: Docker Compose</h3><p><strong>Prompt</strong>:</p><blockquote><p>&#8220;Create /opt/monitoring/docker-compose.yml. Services: prometheus (9090), node-exporter (9100), alertmanager (9093), uptime-kuma (3001). <strong>Security</strong>: Bind ports to the <strong>Tailscale IP</strong> (100.x.x.x), not 127.0.0.1. This allows direct access from your Tailnet while keeping them off the public internet. Example: - &#8216;100.77.226.26:9090:9090&#8217; Volumes: Map config files and persistent data.&#8221;</p></blockquote><p><em>Alternative: Bind to 127.0.0.1 and use SSH tunnels. Tailscale binding is simpler and equally secure within your mesh.</em></p><h3>Step 3: Config Files</h3><p><strong>Prompt</strong>:</p><blockquote><p>&#8220;Create prometheus.yml scraping localhost:9100. Create alertmanager.yml configured for AWS SES (SMTP). <strong>Secrets</strong>: Use environment variables or placeholders for SMTP creds.&#8221;</p></blockquote><h2>4. Alerting Pipelines</h2><p>Graphs are for post-mortems. Alerts are for now. For critical infrastructure, you need an automated pager.</p><h3>A. AWS SES Setup - Your Email Gateway</h3><p>To send reliable emails from your server, you need an SMTP relay. AWS SES is highly recommended for its reliability, low cost, and deliverability. We will manage this through the AWS CLI.</p><h4>Step 1: Configure AWS CLI Credentials (Local)</h4><p>You&#8217;ll need an AWS IAM user with permissions to manage SES identities and send emails. For simplicity in this guide, we&#8217;ll assume your aws configure is set up with an Administrator Access Key. In production, use a more granular IAM policy.</p><p><strong>Agent</strong>: Claude Code (Local, on your Admin Laptop) <strong>Prompt</strong>:</p><blockquote><p>&#8220;I need to configure my local aws-cli with credentials for an IAM user that has permissions to verify SES identities and send emails.</p></blockquote><ol><li><p>Instruct me on how to obtain an AWS Access Key ID and Secret Access Key for an IAM user (e.g., via AWS Console -&gt; IAM -&gt; Users -&gt; Security credentials).</p></li><li><p>Guide me through running aws configure to set up these credentials locally.</p></li><li><p>Remind me to set a default region (e.g., us-east-1) and output format (e.g., json).&#8221;</p></li></ol><h4>Step 2: Verify Email Identity via AWS CLI</h4><p>Before Alertmanager can send emails, your sending email address must be verified with SES.</p><p><strong>Agent</strong>: Claude Code (Local, using aws-cli via deploy user with AWS credentials configured) <strong>Prompt</strong>:</p><blockquote><p>&#8220;I need to verify an email identity for AWS SES for sending alerts. Use aws-cli to verify alerts@yourdomain.com. Command: aws ses verify-email-identity --email-address alerts@yourdomain.com Explain that I will receive a verification email and need to click a link. Then, explain how to check the verification status: aws ses list-identities --query &#8216;Identities[*]&#8217;.&#8221;</p></blockquote><h3>B. The Rules (rules.yml)</h3><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Create prometheus/rules.yml. Rules:</p></blockquote><ol><li><p><strong>InstanceDown</strong>: up == 0 for 1m.</p></li><li><p><strong>HighCPU</strong>: Usage &gt; 90% for 5m.</p></li><li><p><strong>DiskSpace</strong>: Free &lt; 10%. Configure Alertmanager to route these to my email.&#8221;</p></li></ol><h4>Step 1: Verify Email Identity via AWS CLI</h4><p>Before Alertmanager can send emails, your sending email address must be verified with SES.</p><p><strong>Agent</strong>: Claude Code (Local, using aws-cli via deploy user with AWS credentials configured) <strong>Prompt</strong>:</p><blockquote><p>&#8220;I need to verify an email identity for AWS SES for sending alerts. Use aws-cli to verify alerts@yourdomain.com. Command: aws ses verify-email-identity --email-address alerts@yourdomain.com Explain that I will receive a verification email and need to click a link.&#8221;</p></blockquote><h2>5. TLS Certificate Monitoring</h2><p>Cloudflare handles edge certificates, but you still need to monitor:</p><ol><li><p><strong>Edge certificates</strong> (Cloudflare-issued): Rarely expire but can have issues</p></li><li><p><strong>Origin certificates</strong> (Traefik/Let&#8217;s Encrypt): Expire every 90 days</p></li></ol><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Create /opt/monitoring/scripts/check_tls_expiry.sh:</p><p>#!/bin/bash</p><p>DOMAIN=$1</p><p>WARN_DAYS=${2:-21}</p><p>EXPIRY=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2&gt;/dev/null | openssl x509 -noout -enddate | cut -d= -f2)</p><p>EXPIRY_EPOCH=$(date -d &#8220;$EXPIRY&#8221; +%s)</p><p>NOW_EPOCH=$(date +%s)</p><p>DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))</p><p>if [ $DAYS_LEFT -lt $WARN_DAYS ]; then</p><p>  echo &#8220;WARNING: $DOMAIN expires in $DAYS_LEFT days&#8221;</p><p>  exit 1</p><p>fi</p><p>echo &#8220;OK: $DOMAIN expires in $DAYS_LEFT days&#8221;</p></blockquote><ol><li><p>Make executable: chmod +x /opt/monitoring/scripts/check_tls_expiry.sh</p></li><li><p>Add cron: /etc/cron.d/tls_expiry_check</p></li></ol><blockquote><p>0 6 * * * root /opt/monitoring/scripts/check_tls_expiry.sh yourdomain.com 21 &gt;&gt; /var/log/tls-expiry-check.log 2&gt;&amp;1</p></blockquote><ol start="3"><li><p>For Traefik origin certs, create a similar script checking the cert files directly.&#8221;</p></li></ol><h2>6. External Monitoring (UptimeRobot)</h2><p>Internal monitoring can&#8217;t catch issues affecting external access (DNS, Cloudflare outages, etc.). Use an external service.</p><p><strong>Setup</strong>:</p><ol><li><p>Create free account at <a href="https://uptimerobot.com">UptimeRobot</a></p></li><li><p>Add HTTP(S) monitors for each public domain</p></li><li><p>Configure email alerts (same address as Alertmanager)</p></li><li><p><strong>Optional</strong>: Use UptimeRobot API to manage monitors as code:</p></li></ol><blockquote><p>curl -X POST &#8220;https://api.uptimerobot.com/v2/newMonitor&#8221; \</p><p>  -d &#8220;api_key=YOUR_KEY&#8221; \</p><p>  -d &#8220;friendly_name=My App&#8221; \</p><p>  -d &#8220;url=https://myapp.com&#8221; \</p><p>  -d &#8220;type=1&#8221;  # HTTP(S)</p></blockquote><p><em>Alternative services: Pingdom, Better Uptime, or self-hosted with a separate VPS in a different region.</em></p><h2>7. Alertmanager Email Formatting</h2><p>Customize Alertmanager to send clear, actionable alerts.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Update /opt/monitoring/alertmanager.yml:</p><p>global:</p><p>  smtp_smarthost: &#8216;email-smtp.us-east-1.amazonaws.com:587&#8217;</p><p>  smtp_from: &#8216;alerts@yourdomain.com&#8217;</p><p>  smtp_auth_username: &#8216;{{ env &#8220;ALERT_SMTP_USER&#8221; }}&#8217;</p><p>  smtp_auth_password: &#8216;{{ env &#8220;ALERT_SMTP_PASS&#8221; }}&#8217;</p><p>route:</p><p>  receiver: &#8216;email-alerts&#8217;</p><p>  group_by: [&#8217;alertname&#8217;]</p><p>  group_wait: 30s</p><p>  group_interval: 5m</p><p>  repeat_interval: 4h</p><p>receivers:</p><p>  - name: &#8216;email-alerts&#8217;</p><p>    email_configs:</p><p>      - to: &#8216;ops@yourdomain.com&#8217;</p><p>        send_resolved: true</p><p>        headers:</p><p>          Subject: &#8216;[infra] {{ .GroupLabels.alertname }}&#8217;</p><p>Store SMTP credentials in /opt/monitoring/.env (chmod 600). Reload: curl -s -X POST http://localhost:9093/-/reload&#8221;</p></blockquote><h2>8. Verification</h2><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Deploy: docker compose up -d. <strong>Test</strong>: Stop node-exporter. Wait 2 mins. Did I get an email with [infra] subject? Restart node-exporter.&#8221;</p></blockquote><h2>9. Context Update</h2><p><strong>Agent</strong>: Codex <strong>Prompt</strong>:</p><blockquote><p>&#8220;Monitoring Active. Access: Tailscale IPs - Prometheus (9090), Uptime Kuma (3001), Alertmanager (9093). Alerts: SES Email with [infra] prefix. External: UptimeRobot for public domain checks. TLS: Daily cron checks for certificate expiry. Update INFRASTRUCTURE_CONTEXT.md.&#8221;</p></blockquote><div><hr></div><p><strong>Next Step</strong>: We have eyes. Now let&#8217;s secure the front door. Go to <a href="https://open.substack.com/pub/cvsloane/p/the-edge-layer?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">07. The Edge Layer</a>.</p>]]></content:encoded></item><item><title><![CDATA[The App Engine]]></title><description><![CDATA[Objective: Transform the raw apps-vps into a robust PaaS (Platform as a Service) using Docker Engine and Coolify. We will replace the functionality of Vercel with open-source software you control.]]></description><link>https://cvsloane.substack.com/p/the-app-engine</link><guid isPermaLink="false">https://cvsloane.substack.com/p/the-app-engine</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 19:38:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!kVIo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kVIo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kVIo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kVIo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kVIo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kVIo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kVIo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:555755,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180052063?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kVIo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kVIo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kVIo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kVIo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb58857fb-d7ba-4056-8e14-971571734732_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Transform the raw apps-vps into a robust PaaS (Platform as a Service) using <strong>Docker Engine</strong> and <strong>Coolify</strong>. We will replace the functionality of Vercel with open-source software you control.</p><h2>1. The Architecture: &#8220;PaaS in a Box&#8221;</h2><p>We are building a stack that mimics Vercel&#8217;s core features:</p><ol><li><p><strong>Git Push -&gt; Deploy</strong>: Handled by Coolify + GitHub App.</p></li><li><p><strong>SSL Management</strong>: Handled by Traefik (part of Coolify) + Let&#8217;s Encrypt.</p></li><li><p><strong>Environment Management</strong>: Handled by Coolify UI.</p></li><li><p><strong>Containerization</strong>: Handled by Docker.</p></li></ol><h3>Why Docker Engine?</h3><p>We use the <strong>Official Docker Engine</strong>, not the Ubuntu &#8220;Snap&#8221; package.</p><ul><li><p><strong>Snap</strong>: Auto-updates can break your db. Permission issues with volumes.</p></li><li><p><strong>Official</strong>: Stable, predictable, standard paths (/var/lib/docker).</p></li></ul><h2>2. Preparing the Host</h2><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;SSH into apps-vps.</p></blockquote><ol><li><p><strong>Remove Snaps</strong>: sudo apt remove docker.io docker-doc ....</p></li><li><p><strong>Install Official</strong>: Add Docker GPG key and repo. Install docker-ce docker-ce-cli containerd.io.</p></li><li><p><strong>User Group</strong>: sudo usermod -aG docker deploy. (Requires re-login).</p></li><li><p><strong>Log Rotation</strong>: Create /etc/docker/daemon.json.</p></li></ol><blockquote><p>{ &#8220;log-driver&#8221;: &#8220;json-file&#8221;, &#8220;log-opts&#8221;: { &#8220;max-size&#8221;: &#8220;10m&#8221;, &#8220;max-file&#8221;: &#8220;3&#8221; } }</p><p><em>Why?</em>: Without this, a looping error fills your disk in 24 hours.</p></blockquote><ol start="5"><li><p>Restart Docker.&#8221;</p></li></ol><h2>3. Installing Coolify</h2><p>Coolify is our orchestrator.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;SSH into apps-vps. Run the install script: curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash. Wait for completion. Verify containers: docker ps | grep coolify.&#8221;</p></blockquote><h2>4. Initial Configuration</h2><ol><li><p><strong>Access</strong>: Two paths, one public, one private:</p><ul><li><p><strong>Public (Proxied)</strong>: https://coolify.yourdomain.com (via Cloudflare proxy on 443, fronted by Traefik). Use this for normal admin access; enforce 2FA.</p></li><li><p><strong>Private (Direct)</strong>: http://100.x.y.z:8000 (Tailscale IP only).</p></li><li><p><strong>Security Rule</strong>: UFW must <strong>deny 8000 on eth0</strong> and <strong>allow 8000 on tailscale0</strong>. Keep 80/443 open. This keeps the raw admin port off the public internet while preserving a VPN backdoor.</p></li></ul></li><li><p><strong>Register</strong>: Create Admin account.</p></li><li><p><strong>Security Hardening (Critical)</strong>:</p><ul><li><p><strong>2FA</strong>: Enable Two-Factor Authentication immediately (Profile -&gt; Security).</p></li><li><p><strong>Break-Glass Account</strong>: Create a second admin user (e.g., breakglass@yourdomain.com) with a generated 32-char password. Store this offline (e.g., on paper or in a separate vault). If 2FA fails or SSO breaks, this is your only way back in.</p></li></ul></li><li><p><strong>Select Server</strong>: Choose &#8220;Local Docker&#8221;.</p></li><li><p><strong>GitHub Integration</strong>:</p><ul><li><p>Go to Sources -&gt; Add GitHub.</p></li><li><p>Create GitHub App (Follow Coolify prompts).</p></li><li><p><strong>Crucial</strong>: Grant access ONLY to specific repos (not your whole org).</p></li></ul></li></ol><h2>5. Coolify Internal Tuning (Horizon Queues)</h2><p>Coolify uses Laravel Horizon to manage background jobs (deployments, server checks). By default, a heavy deployment can clog the queue, preventing other tasks (like backups) from running. We will tune the worker pools.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;SSH into apps-vps. I need to check the Horizon supervisor configuration inside the Coolify container.</p></blockquote><ol><li><p>Run docker exec coolify php artisan horizon:supervisors.</p></li><li><p>We want to ensure there are dedicated queues.</p><ul><li><p>deployments: For long-running builds.</p></li><li><p>priority: For server health checks.</p></li><li><p>general: For everything else.</p></li></ul></li><li><p>If they are not split, we might need to edit the docker-compose.yml or environment variables for Coolify to set HORIZON_BALANCE=simple or custom supervisor configurations. <em>(Note: In recent Coolify versions, this is auto-managed, but verifying 3 separate pools is best practice.)</em></p></li><li><p>Also, check if balanceMaxShift is set to 2 (allows workers to shift pools quickly).&#8221;</p></li></ol><h2>6. Docker Firewall Workaround (Critical)</h2><p><strong>Warning</strong>: Docker bypasses UFW by default! A simple ufw deny 8000 will NOT block Docker-exposed ports.</p><p>Docker manipulates iptables directly via the DOCKER chain, which is processed before UFW rules. To properly restrict Docker ports, we must use the DOCKER-USER chain.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;SSH into apps-vps. We need to block port 8000 on the public interface while allowing it on Tailscale.</p></blockquote><ol><li><p>Create iptables rules:</p></li></ol><blockquote><p># Drop 8000 from public interface</p><p>sudo iptables -I DOCKER-USER -i eth0 -p tcp --dport 8000 -j DROP</p><p># Allow 8000 from Tailscale</p><p>sudo iptables -I DOCKER-USER -i tailscale0 -p tcp --dport 8000 -j ACCEPT</p></blockquote><ol start="2"><li><p>Persist rules across reboot:</p></li></ol><blockquote><p>sudo apt install iptables-persistent netfilter-persistent</p><p>sudo netfilter-persistent save</p></blockquote><ol start="3"><li><p>Verify: Run sudo iptables -L DOCKER-USER -n -v to see the rules.&#8221;</p></li></ol><p><em>Note: The interface name may vary (eth0, ens3, etc.). Check with ip addr.</em></p><h2>7. Security Verification</h2><p>Ensure the Coolify Dashboard is NOT exposed to the wild.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Check public access to port 8000. nc -zv &lt;PUBLIC_IP&gt; 8000. It MUST fail (Timeout/Refused). If it succeeds, check DOCKER-USER iptables rules immediately.&#8221;</p></blockquote><h2>8. Redis Queue (Optional)</h2><p>For applications using background jobs (BullMQ, Sidekiq, etc.), deploy Redis on the Apps VPS.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;SSH into apps-vps. Create /opt/redis/docker-compose.yml:</p><p>services:</p><p>  redis:</p><p>    container_name: redis-broker</p><p>    image: redis:7.4-alpine</p><p>    restart: unless-stopped</p><p>    command: redis-server --appendonly yes --requirepass &lt;STRONG_PASSWORD&gt;</p><p>    volumes:</p><p>      - ./data:/data</p><p>    ports:</p><p>      - &#8216;100.x.x.x:6379:6379&#8217;  # Tailscale IP only</p></blockquote><ol><li><p>Generate password: openssl rand -base64 32</p></li><li><p>Create directory: mkdir -p /opt/redis/data</p></li><li><p>Deploy: cd /opt/redis &amp;&amp; docker compose up -d</p></li><li><p>Apply system tuning:</p><ul><li><p>Add vm.overcommit_memory = 1 to /etc/sysctl.d/99-redis.conf</p></li><li><p>Disable Transparent Huge Pages (THP) via systemd service</p></li></ul></li><li><p>Verify: redis-cli -h 100.x.x.x -a &lt;password&gt; ping&#8221;</p></li></ol><p><em>For external access (e.g., Vercel during migration), consider adding mTLS on a separate port (6380).</em></p><h2>9. Context Update</h2><p><strong>Agent</strong>: Codex <strong>Prompt</strong>:</p><blockquote><p>&#8220;Coolify Installed &amp; Hardened. Dashboard: http://apps-vps:8000 (Tailscale-only via DOCKER-USER rules). Security: 2FA Enabled, Break-glass account created. Tuning: Horizon queues verified. Redis: Available at 100.x.x.x:6379 (optional). Update INFRASTRUCTURE_CONTEXT.md.&#8221;</p></blockquote><div><hr></div><p><strong>Next Step</strong>: We have a platform. Now we need eyes on it. Go to <a href="https://open.substack.com/pub/cvsloane/p/observability-stack?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">06. Observability Stack</a>.</p>]]></content:encoded></item><item><title><![CDATA[The Private Mesh]]></title><description><![CDATA[Objective: Connect the isolated apps-vps and db-vps using Tailscale, creating a private, encrypted mesh network.]]></description><link>https://cvsloane.substack.com/p/the-private-mesh</link><guid isPermaLink="false">https://cvsloane.substack.com/p/the-private-mesh</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 19:34:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!6pSN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6pSN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6pSN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6pSN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6pSN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6pSN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6pSN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:661439,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180051773?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6pSN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6pSN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6pSN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6pSN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3134d09-1581-4e63-a9af-f5dfda1b5ff9_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Connect the isolated apps-vps and db-vps using <strong>Tailscale</strong>, creating a private, encrypted mesh network. We will implement &#8220;Zero Trust&#8221; principles by configuring Firewalls and Tailscale ACLs to ensure the Database is only accessible by the App Server, and never the public internet.</p><h2>1. The &#8220;Public IP&#8221; Problem</h2><p>In a traditional setup, to let your App Server talk to your Database:</p><ol><li><p>You open Port 5432 on the DB Server&#8217;s <strong>Public IP</strong>.</p></li><li><p>You rely on IP Whitelisting (pg_hba.conf or UFW).</p></li><li><p><strong>The Risk</strong>: If you mess up the whitelist, or if IPs change, your Database is exposed to the entire internet. IP spoofing is hard, but misconfiguration is easy.</p></li></ol><p><strong>The Solution: Overlay Networks</strong>. We will create a virtual network (100.x.y.z) that exists <em>on top</em> of the public internet. Traffic is encrypted (WireGuard). The DB Server listens <em>only</em> on this virtual interface.</p><h2>2. Installing Tailscale via AI</h2><p>We need to install the agent on 3 nodes: Admin Laptop, Apps VPS, DB VPS.</p><h3>Step 1: Server Installation</h3><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;SSH into apps-vps. Install Tailscale using the official script: curl -fsSL https://tailscale.com/install.sh | sh Run sudo tailscale up. <strong>Important</strong>: Capture the Auth URL and show it to me.&#8221;</p></blockquote><p><em>Click the URL to authorize. Repeat for db-vps.</em></p><h3>Step 2: MagicDNS &amp; Naming</h3><ol><li><p>Go to <strong>Tailscale Admin Console</strong>.</p></li><li><p>Rename the machines to apps-vps and db-vps (removing the random suffixes).</p></li><li><p>Enable <strong>MagicDNS</strong>. This allows us to ping db-vps instead of memorizing IPs.</p></li></ol><h2>3. The Firewall Trust (The &#8220;Air Gap&#8221;)</h2><p>Currently, UFW on db-vps blocks <strong>everything</strong> (except SSH on Public IP). It also blocks the Tailscale interface by default. We need to tell UFW: <strong>&#8220;Trust the Mesh.&#8221;</strong></p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;SSH into db-vps. We need to allow traffic from the Tailscale interface (tailscale0).</p></blockquote><ol><li><p>Check interface name: ip addr show tailscale0.</p></li><li><p>Add rule: sudo ufw allow in on tailscale0.</p><ul><li><p><em>Why</em>: This allows port 5432 (and others) ONLY if the packet comes decrypted from the VPN.</p></li></ul></li><li><p>Reload UFW.</p></li><li><p>Verify with sudo ufw status verbose.&#8221;</p></li></ol><p><em>Repeat for apps-vps (allows SSH from laptop via VPN).</em></p><h2>4. Tailscale ACLs (Advanced Security)</h2><p>For true &#8220;Zero Trust,&#8221; we don&#8217;t just trust everyone on the VPN. We restrict access.</p><p><strong>Agent</strong>: Gemini CLI <strong>Prompt</strong>:</p><blockquote><p>&#8220;I have 3 nodes: admin, apps, db. Generate a Tailscale ACL JSON policy. Rules:</p></blockquote><ol><li><p><strong>Admin</strong>: Access to *:* (Everything).</p></li><li><p><strong>Apps</strong>: Access to tag:db:5432 (Postgres) and tag:db:6543 (PgBouncer).</p></li><li><p><strong>Isolation</strong>: db cannot initiate connections to apps (only reply).</p></li><li><p><strong>Default</strong>: Deny all.&#8221;</p></li></ol><p><em>Paste this JSON into Tailscale Admin Console -&gt; Access Controls.</em></p><h2>5. Connectivity Verification</h2><p>Let&#8217;s prove it works.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;SSH into apps-vps.</p></blockquote><ol><li><p>Ping the DB: ping -c 4 db-vps.</p></li><li><p>Check SSH port via Mesh: nc -zv db-vps 22.</p></li><li><p>Check Public IP for 5432 (Should fail/timeout). Report results.&#8221;</p></li></ol><h2>6. SSH Lockdown (Critical Security Step)</h2><p>Now that the mesh is operational, we <strong>block public SSH</strong> entirely. All SSH access will be Tailscale-only.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt (Apps VPS)</strong>:</p><blockquote><p>&#8220;SSH into apps-vps via Tailscale (ssh deploy@100.x.x.x).</p></blockquote><ol><li><p>Remove public SSH rule: sudo ufw delete allow 22/tcp.</p></li><li><p>Verify: sudo ufw status verbose - Port 22 should only appear for tailscale0.</p></li><li><p>Test from <strong>outside Tailscale</strong> (e.g., your phone on mobile data): ssh deploy@&lt;PUBLIC_IP&gt; should timeout.</p></li><li><p>Test from <strong>inside Tailscale</strong>: ssh deploy@apps-vps should work. Report results.&#8221;</p></li></ol><p><em>Repeat for db-vps. The DB VPS already had no public ports - this confirms the lockdown.</em></p><p><strong>Warning</strong>: Before running this, ensure you have VNC access via Contabo control panel as a backup!</p><h2>7. Context Update</h2><p><strong>Agent</strong>: Codex <strong>Prompt</strong>:</p><blockquote><p>&#8220;Mesh Network Active &amp; SSH Locked Down. <strong>Apps VPS</strong>: 100.x.x.x &#8212; Public SSH blocked, Tailscale-only <strong>DB VPS</strong>: 100.y.y.y &#8212; No public ports whatsoever Firewall: in on tailscale0 ALLOWED. Public port 22 DENIED. Update INFRASTRUCTURE_CONTEXT.md.&#8221;</p></blockquote><div><hr></div><p><strong>Next Step</strong>: The network is ready. Time to build the Platform. Go to <a href="https://open.substack.com/pub/cvsloane/p/the-app-engine">05. The App Engine</a>.</p>]]></content:encoded></item><item><title><![CDATA[The Metal Foundation]]></title><description><![CDATA[Objective: Go from &#8220;Purchase Confirmation Email&#8221; to a fully hardened, secure server environment.]]></description><link>https://cvsloane.substack.com/p/the-metal-foundation</link><guid isPermaLink="false">https://cvsloane.substack.com/p/the-metal-foundation</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 19:23:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CJi7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CJi7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CJi7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!CJi7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!CJi7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!CJi7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CJi7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:742724,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180049800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CJi7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!CJi7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!CJi7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!CJi7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d4170b3-6543-4b78-a4e8-8be879773525_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Go from &#8220;Purchase Confirmation Email&#8221; to a fully hardened, secure server environment. We will use AI agents to generate safe provisioning scripts and configure &#8220;Default Deny&#8221; firewalls.</p><h2>1. Buying the Metal: Provider Selection</h2><p>AI cannot buy servers for you (yet). You must do this manually.</p><h3>The Contabo Configuration (Recommended)</h3><p><em>Why Contabo? Best Price/Performance ratio for raw compute.</em></p><ol><li><p><strong>Apps VPS</strong>: Cloud VPS 20 SSD (6 vCPU, 16GB RAM, 200GB NVMe).</p></li><li><p><strong>DB VPS</strong>: Cloud VPS 20 SSD (6 vCPU, 16GB RAM, 200GB NVMe) - dedicated resources for Postgres.</p></li><li><p><strong>Region</strong>: Choose the one closest to your users. <strong>Crucial</strong>: Both VPSs MUST be in the same region to minimize latency.</p></li><li><p><strong>Storage</strong>: <strong>NVMe</strong>. Never choose SSD if NVMe is an option. Database performance depends on IOPS.</p></li><li><p><strong>OS</strong>: <strong>Ubuntu 24.04 LTS</strong>. Use the latest Long Term Support.</p></li></ol><p><em>Note: Product names vary by promotion. The key specs are 6+ vCPU, 16GB+ RAM, and NVMe storage.</em></p><p><em>After purchase, wait 15-30 mins for the email with IPs and Root Passwords.</em></p><h2>2. The &#8220;First Boot&#8221; Ritual</h2><p><strong>Warning</strong>: The moment you log in as root, you are vulnerable. If you edit SSH configs incorrectly, you lock yourself out. <strong>Strategy</strong>: We use <strong>Claude Code</strong> to generate a &#8220;One-Shot&#8221; setup script that creates a sudo user and installs keys <em>before</em> we touch any configs.</p><h3>Step 1: Generate Infrastructure Keys</h3><p>Don&#8217;t use your personal GitHub key. Create a dedicated key for this cluster.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Check ~/.ssh/. Do I have a key named id_ed25519_vps? If not, run ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_vps -C &#8216;admin@infra&#8217;. Display the public key content so I can copy it.&#8221;</p></blockquote><h3>Step 2: The Provisioning Script</h3><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;I have a fresh Ubuntu 24.04 VPS. Write a bash script to initialize a user. <strong>Variables</strong>: USER=deploy, PUBKEY=&lt;PASTE_KEY_HERE&gt;. <strong>Actions</strong>:</p></blockquote><ol><li><p>Create user $USER with bash shell.</p></li><li><p>Add to sudo group.</p></li><li><p>Create .ssh dir with 700 permissions.</p></li><li><p>Create authorized_keys with 600 permissions and append the key.</p></li><li><p>Configure sudoers to allow $USER sudo without password (optional but useful for agents). Show me the script.&#8221;</p></li></ol><p><strong>Action</strong>: SSH into root@&lt;IP&gt;, paste the script, run it. Repeat for both servers.</p><h3>Step 3: Verify Access</h3><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Verify access to 144.126.x.x using the new user. ssh -i ~/.ssh/id_ed25519_vps deploy@144.126.x.x &#8216;whoami&#8217; It should return &#8216;deploy&#8217;.&#8221;</p></blockquote><h2>3. Agent-Driven OS Hardening</h2><p>Now that we have secure access, we lock the doors.</p><h3>A. SSH Hardening (sshd_config)</h3><p>Default Ubuntu allows Root Login and Password Auth. We want neither.</p><p><strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;SSH into apps-vps. We are hardening SSH.</p></blockquote><ol><li><p>Backup config: sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.</p></li><li><p>Edit /etc/ssh/sshd_config:</p><ul><li><p>PermitRootLogin no</p></li><li><p>PasswordAuthentication no</p></li><li><p>ChallengeResponseAuthentication no</p></li><li><p>UsePAM yes</p></li><li><p>X11Forwarding no</p></li></ul></li><li><p><strong>Critical</strong>: Run sudo sshd -t to verify syntax. If error, abort.</p></li><li><p>Restart SSH.</p></li><li><p><strong>Suicide Check</strong>: Immediately try to open a <em>second</em> SSH connection in the background. If it fails, revert the config.&#8221;</p></li></ol><p><em>Repeat for db-vps.</em></p><h3>B. Firewall Strategy (UFW)</h3><p>We adopt a <strong>&#8220;Default Deny&#8221;</strong> posture.</p><p><strong>Apps VPS Rules</strong>: <strong>Agent</strong>: Claude Code <strong>Prompt</strong>:</p><blockquote><p>&#8220;Configure UFW on apps-vps.</p></blockquote><ol><li><p>sudo ufw default deny incoming.</p></li><li><p>sudo ufw default allow outgoing.</p></li><li><p>sudo ufw allow 22/tcp (SSH).</p></li><li><p>sudo ufw allow 80/tcp (HTTP).</p></li><li><p>sudo ufw allow 443/tcp (HTTPS).</p></li><li><p>Enable and verify.&#8221;</p></li></ol><p><strong>DB VPS Rules</strong>: <strong>Prompt</strong>:</p><blockquote><p>&#8220;Configure UFW on db-vps.</p></blockquote><ol><li><p>sudo ufw default deny incoming.</p></li><li><p>sudo ufw default allow outgoing.</p></li><li><p>sudo ufw allow 22/tcp (SSH).</p></li><li><p><strong>Do NOT</strong> open port 5432 yet.</p></li><li><p>Enable and verify.&#8221;</p></li></ol><h2>4. System Updates &amp; Hygiene</h2><p><strong>Prompt</strong>:</p><blockquote><p>&#8220;SSH into both servers.</p></blockquote><ol><li><p>Run sudo apt update &amp;&amp; sudo apt upgrade -y.</p></li><li><p>Install tools: curl wget git vim htop net-tools ncdu.</p></li><li><p>Set Hostnames: sudo hostnamectl set-hostname apps-vps (and db-vps).</p></li><li><p>Set Timezone: sudo timedatectl set-timezone UTC.</p></li><li><p>Setup Swap: If no swap, create a 4GB swapfile (essential for stability).&#8221;</p></li></ol><h2>5. Context Update</h2><p><strong>Agent</strong>: Codex <strong>Prompt</strong>:</p><blockquote><p>&#8220;Provisioning &amp; Hardening Complete. <strong>Apps VPS</strong>: 144.126.x.x (User: deploy). Ports 22/80/443 Open. <strong>DB VPS</strong>: 144.126.y.y (User: deploy). Port 22 Open. SSH Hardening: Keys only. Root disabled. Update INFRASTRUCTURE_CONTEXT.md.&#8221;</p></blockquote><p><strong>Important</strong>: After completing the Tailscale mesh in the next module, we will <strong>block public SSH entirely</strong> (port 22 on the public interface). All SSH access will then be Tailscale-only, adding another layer of security. For now, port 22 remains open on the public IP for initial setup only.</p><div><hr></div><p><strong>Next Step</strong>: The servers are secure islands. We need to build a bridge. Go to <a href="https://open.substack.com/pub/cvsloane/p/the-private-mesh?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">04. The Private Mesh</a>.</p>]]></content:encoded></item><item><title><![CDATA[The Case for Self-Hosting]]></title><description><![CDATA[Objective: Perform a rigorous financial and operational analysis of the &#8220;Exit.&#8221; We will define the &#8220;Safe Harbor&#8221; 2-node architecture, calculate the precise ROI of leaving managed cloud services, and understand why &#8220;owning the metal&#8221; is the ultimate competitive advantage for a scaling startup.]]></description><link>https://cvsloane.substack.com/p/the-case-for-self-hosting</link><guid isPermaLink="false">https://cvsloane.substack.com/p/the-case-for-self-hosting</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 19:22:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!R1CM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R1CM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R1CM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!R1CM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!R1CM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!R1CM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R1CM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:659213,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180046904?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!R1CM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!R1CM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!R1CM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!R1CM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f97a6e9-bb23-40e6-b5e8-e9554da63562_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Objective</strong>: Perform a rigorous financial and operational analysis of the &#8220;Exit.&#8221; We will define the &#8220;Safe Harbor&#8221; 2-node architecture, calculate the precise ROI of leaving managed cloud services, and understand why &#8220;owning the metal&#8221; is the ultimate competitive advantage for a scaling startup.</p><h2>1. The Economics of Exit: Escaping the Success Tax</h2><p>Vercel and Supabase are fantastic &#8220;Day 0&#8221; tools. They offer zero-config deployment. But their business model depends on the <strong>&#8220;Success Tax.&#8221;</strong> As you scale, their pricing curves become punitive.</p><h3>The &#8220;Success Tax&#8221; Breakdown (Scenario: Moderate B2B SaaS)</h3><p><em>Traffic: 1TB Bandwidth, 500k Users, 50GB DB.</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Wk5I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Wk5I!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png 424w, https://substackcdn.com/image/fetch/$s_!Wk5I!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png 848w, https://substackcdn.com/image/fetch/$s_!Wk5I!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png 1272w, https://substackcdn.com/image/fetch/$s_!Wk5I!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Wk5I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png" width="1052" height="446" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:446,&quot;width&quot;:1052,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:85711,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180046904?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Wk5I!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png 424w, https://substackcdn.com/image/fetch/$s_!Wk5I!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png 848w, https://substackcdn.com/image/fetch/$s_!Wk5I!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png 1272w, https://substackcdn.com/image/fetch/$s_!Wk5I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2cdb98d-7e86-4763-8bfd-0e362e8c7fdd_1052x446.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>The Self-Hosted Bill (Monthly)</h4><p>We replace &#8220;Variable Usage Pricing&#8221; with &#8220;Fixed Capacity Pricing.&#8221;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N0-H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N0-H!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png 424w, https://substackcdn.com/image/fetch/$s_!N0-H!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png 848w, https://substackcdn.com/image/fetch/$s_!N0-H!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png 1272w, https://substackcdn.com/image/fetch/$s_!N0-H!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N0-H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png" width="1064" height="478" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:478,&quot;width&quot;:1064,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:100449,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180046904?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!N0-H!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png 424w, https://substackcdn.com/image/fetch/$s_!N0-H!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png 848w, https://substackcdn.com/image/fetch/$s_!N0-H!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png 1272w, https://substackcdn.com/image/fetch/$s_!N0-H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac8ef21-bef4-42b2-9876-21a16df97c10_1064x478.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>The Savings</strong>: <strong>~90% to 97%</strong>. <strong>The ROI</strong>: If migration takes 10 hours, and you save $530/mo ($6360/yr), your hourly rate for this work is <strong>$636/hr</strong>.</p><h2>2. The Operational Case: Visibility &amp; Control</h2><p>Saving money is great. But <strong>Control</strong> is better.</p><h3>The &#8220;Black Box&#8221; Problem</h3><p>On Vercel, if your API is slow, you see a chart saying &#8220;High Duration.&#8221;</p><ul><li><p>Is it the CPU?</p></li><li><p>Is it a &#8220;Noisy Neighbor&#8221; on the AWS Lambda host?</p></li><li><p>Is it a network timeout? <strong>You cannot know.</strong> You don&#8217;t have htop. You don&#8217;t have root.</p></li></ul><h3>The &#8220;Glass Box&#8221; Solution</h3><p>On your VPS:</p><ol><li><p><strong>Unified Logs</strong>: You run docker logs -f and see Nginx, Next.js, and Redis logs interleaved. You see the <em>causal chain</em> of errors immediately.</p></li><li><p><strong>Deep Metrics</strong>: You install Node Exporter. You see &#8220;CPU Core 2 is pinned by ffmpeg.&#8221; You fix the code. You don&#8217;t just pay for a bigger tier.</p></li><li><p><strong>No Vendor Lock-in</strong>: You use standard Docker images. If Contabo doubles prices, you rsync your volumes to Hetzner in 1 hour. You are sovereign.</p></li></ol><h2>3. The &#8220;Safe Harbor&#8221; Architecture</h2><p>We do not dump everything on one server. That is a Single Point of Failure (SPOF). We build a <strong>2-Node Cluster</strong> connected by a private mesh.</p><h3>Why Two Nodes?</h3><ol><li><p><strong>The &#8220;OOM Killer&#8221; Protection</strong>: Next.js is memory hungry. If it eats all RAM, Linux kills the biggest process. On a single server, that&#8217;s often <strong>Postgres</strong>. By separating them, your App can crash without taking down your Data.</p></li><li><p><strong>The Security Air Gap</strong>: The App Server <em>must</em> be exposed to the internet (Port 443). The DB Server <em>never</em> needs to be. By isolating the DB on a separate node with <strong>NO public ports</strong>, we create a massive security barrier.</p></li></ol><h3>The Diagram</h3><p>graph TD</p><p>    User((User)) --&gt;|HTTPS:443| CF[Cloudflare Proxy]</p><p>    CF --&gt;|HTTPS:443| AppsVPS[Apps VPS (Public)]</p><p>    subgraph &#8220;Public Internet&#8221;</p><p>        AppsVPS</p><p>    end</p><p>    subgraph &#8220;Private Mesh (Tailscale 100.x.x.x)&#8221;</p><p>        AppsVPS -- &#8220;Postgres (Port 6432)&#8221; --&gt; DBVPS[DB VPS (Private)]</p><p>        Laptop[Admin] -- &#8220;SSH (22)&#8221; --&gt; AppsVPS</p><p>        Laptop -- &#8220;SSH (22)&#8221; --&gt; DBVPS</p><p>    end</p><p>    subgraph &#8220;Apps VPS (The Cattle)&#8221;</p><p>        Coolify[Coolify PaaS]</p><p>        Traefik[Traefik Proxy]</p><p>        NextJS[Next.js App]</p><p>        Redis[Redis Cache]</p><p>    end</p><p>    subgraph &#8220;DB VPS (The Pet)&#8221;</p><p>        PgBouncer[Connection Pooler]</p><p>        Postgres[Postgres 16]</p><p>        WALG[Backup Agent]</p><p>    end</p><p>    WALG -.-&gt;|Encrypted Backup| R2[Cloudflare R2]</p><h3>Node Specs</h3><ul><li><p><strong>Apps VPS</strong>: High CPU (for SSR rendering). Run <strong>Coolify</strong>.</p></li><li><p><strong>DB VPS</strong>: High RAM (for caching). Fast NVMe. Run <strong>Docker Compose</strong>.</p></li></ul><h2>4. The Edge Layer: Cloudflare API</h2><p>We are not just buying servers. We are putting <strong>Cloudflare</strong> in front of them.</p><ul><li><p><strong>DDoS Protection</strong>: Hides our real IP.</p></li><li><p><strong>SSL</strong>: &#8220;Strict&#8221; mode handles crypto at the edge.</p></li><li><p><strong>Management</strong>: We use API scripts (curl) to manage DNS as code.</p></li></ul><h2>5. The Network: Tailscale Mesh</h2><p>How do servers talk if the DB has no public ports? <strong>Tailscale</strong>.</p><ul><li><p>It creates a virtual network (100.x.y.z).</p></li><li><p>Traffic is encrypted (WireGuard).</p></li><li><p>We configure the DB Firewall to <strong>Drop All</strong> from Public IPs and <strong>Accept All</strong> from Tailscale IPs.</p></li></ul><div><hr></div><p><strong>Next Step</strong>: We have the blueprint. Let&#8217;s buy the metal. <a href="https://open.substack.com/pub/cvsloane/p/the-metal-foundation?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">Go to 03. The Metal Foundation</a>.</p>]]></content:encoded></item><item><title><![CDATA[The AI-Native Ops Workflow]]></title><description><![CDATA[Objective: Establish the tooling, environment, and mental model required to build enterprise-grade infrastructure using AI agents.]]></description><link>https://cvsloane.substack.com/p/the-ai-native-ops-workflow</link><guid isPermaLink="false">https://cvsloane.substack.com/p/the-ai-native-ops-workflow</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Wed, 26 Nov 2025 18:26:01 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/359b2cbe-f60b-4b0c-8fe1-d1ac10a181fc_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!axoL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!axoL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!axoL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!axoL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!axoL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!axoL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:536653,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/180044435?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!axoL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!axoL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!axoL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!axoL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93db1752-6cd8-4856-a43b-c50813671e8e_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Objective</strong>: Establish the tooling, environment, and mental model required to build enterprise-grade infrastructure using AI agents. We will configure the &#8220;Triad&#8221; of tools (Codex, Claude, Gemini) and integrate them with an Obsidian-based &#8220;Context System.&#8221;</p><h2>1. The Paradigm Shift: Architect vs. Operator</h2><p>In the traditional DevOps world, you are the <strong>Operator</strong>.</p><ul><li><p>You memorize flags for tar, rsync, systemctl, and iptables.</p></li><li><p>You manually edit config files in nano, hoping you didn&#8217;t miss a semicolon.</p></li><li><p>Your &#8220;state&#8221; is stored in your brain or a stale Wiki.</p></li><li><p><strong>Bottleneck</strong>: Your typing speed and your memory.</p></li></ul><p>In the <strong>AI-Native Ops</strong> world, you are the <strong>Architect</strong>.</p><ul><li><p>You define the <em>Goal</em>: &#8220;I want a secure, high-availability Postgres cluster.&#8221;</p></li><li><p>You define the <em>Constraints</em>: &#8220;It must use Docker, allow access only via Tailscale, and back up to R2.&#8221;</p></li><li><p>The AI Agents handle the <em>Implementation</em>.</p></li><li><p><strong>Bottleneck</strong>: Your ability to clearly articulate your intent.</p></li></ul><p>This shift requires a new toolchain. We are replacing your memory with <strong>Codex</strong>, your hands with <strong>Claude</strong>, and your research skills with <strong>Gemini</strong>.</p><h2>2. The Toolchain: The Triad &amp; The Utility Knives</h2><p>We will use three distinct CLI agents. Using the wrong one for a task leads to frustration. These are supplemented by powerful utility CLIs for specific cloud providers.</p><h3>A. Codex CLI (The Autonomous Agent)</h3><p><em>OpenAI&#8217;s official CLI tool, powered by GPT-5.1-Codex-Max (as of November 2025).</em></p><ul><li><p><strong>Role</strong>: The Autonomous Executor &amp; Context Manager.</p></li><li><p><strong>Superpower</strong>: Can work autonomously for extended periods (24+ hours), managing long-term memory, project context, and executing complex multi-step tasks. Supports multi-context window operation.</p></li><li><p><strong>Weakness</strong>: Expensive for massive log dumps; requires careful supervision for critical operations.</p></li><li><p><strong>When to use</strong>:</p><ul><li><p>&#8220;What was the IP address of the database server?&#8221;</p></li><li><p>&#8220;Remind me of the architecture decision we made about Redis.&#8221;</p></li><li><p>&#8220;Update the documentation to reflect the new firewall rules.&#8221;</p></li><li><p>Extended autonomous coding sessions.</p></li></ul></li></ul><p><strong>Installation</strong>:</p><p># Via npm (recommended)</p><p>npm i -g @openai/codex</p><p># Or via Homebrew on macOS</p><p>brew install --cask codex</p><p># Authenticate</p><p>codex auth login</p><p># Alias for speed</p><p>alias cx=&#8221;codex&#8221;</p><p><strong>Reference</strong>: <a href="https://openai.com/codex/">OpenAI Codex</a></p><h3>B. Claude Code (The Engineer)</h3><p><em>This is the star of the show.</em> claude-code is an official tool from Anthropic designed specifically for execution. <strong>Current version: 2.0.54</strong>, powered by <strong>Claude Sonnet 4.5</strong> and <strong>Claude Opus 4.5</strong>.</p><ul><li><p><strong>Role</strong>: The Execution Engine.</p></li><li><p><strong>Superpower</strong>: It can <strong>SSH into servers</strong>, run commands, read files, edit files, and fix its own errors. It acts as a Senior Engineer paired with you. Features include checkpoints, /rewind command for session recovery, and VS Code extension integration.</p></li><li><p><strong>Weakness</strong>: Can get stuck in loops if a command fails repeatedly. Needs supervision.</p></li><li><p><strong>When to use</strong>:</p><ul><li><p>&#8220;SSH into apps-vps and install Docker.&#8221;</p></li><li><p>&#8220;Write a Python script to migrate this data and run it.&#8221;</p></li><li><p>&#8220;Edit nginx.conf to enable gzip compression.&#8221;</p></li></ul></li></ul><p><strong>Installation</strong>:</p><p># Requires Node.js 18+</p><p>npm install -g @anthropic-ai/claude-code</p><p>claude auth login</p><p># Alias for speed</p><p>alias c=&#8221;claude&#8221;</p><p><strong>VS Code Extension</strong> (Beta): Search &#8220;Claude Code&#8221; in the VS Code marketplace for IDE integration.</p><p><strong>Reference</strong>: <a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code Documentation</a></p><h3>C. Gemini CLI (The Strategist)</h3><p><em>Gemini 3 Pro (as of November 2025) features thinking models with adaptive reasoning and massive context windows.</em></p><ul><li><p><strong>Role</strong>: The Big Brain / Debugger.</p></li><li><p><strong>Superpower</strong>: Absorbing massive amounts of data (Logs, Documentation) and finding the needle in the haystack. Features Deep Think mode for complex reasoning tasks (available to AI Ultra subscribers).</p></li><li><p><strong>Weakness</strong>: Tool use (running commands) is less mature than Claude Code.</p></li><li><p><strong>When to use</strong>:</p><ul><li><p>&#8220;Here is a 50MB log file. Find the error.&#8221;</p></li><li><p>&#8220;Read this 100-page PDF on PgBouncer and tell me how to configure TLS.&#8221;</p></li><li><p>&#8220;Design a high-availability architecture for this app.&#8221;</p></li></ul></li></ul><p><strong>Installation</strong>: Use the Google Gen AI SDK or a wrapper.</p><p>pip install google-generativeai</p><p># You will need a simple python wrapper script</p><p># export GOOGLE_API_KEY=&#8221;...&#8221;</p><p># alias g=&#8221;python3 ~/scripts/gemini.py&#8221;</p><p><strong>Reference</strong>: <a href="https://ai.google.dev/gemini-api/docs/models">Gemini API Documentation</a></p><h3>D. Cloudflare API (The Edge Manager)</h3><p>We manage our DNS and Edge infrastructure as code using the official API.</p><ul><li><p><strong>Role</strong>: DNS &amp; Storage Manager.</p></li><li><p><strong>Superpower</strong>: Managing Cloudflare resources programmatically.</p></li><li><p><strong>When to use</strong>:</p><ul><li><p>&#8220;Update the DNS record for app.example.com to point to the new VPS.&#8221;</p></li><li><p>&#8220;Create a new R2 bucket for backups.&#8221;</p></li></ul></li></ul><p><strong>Installation</strong>: No installation required. We use curl (pre-installed on most systems).</p><h3>E. AWS CLI (The Cloud Swiss Army Knife)</h3><p>This CLI allows command-line interaction with a vast array of AWS services, including SES (Simple Email Service).</p><ul><li><p><strong>Role</strong>: AWS Resource Manager.</p></li><li><p><strong>Superpower</strong>: Automating the configuration and verification of AWS services directly from the terminal.</p></li><li><p><strong>When to use</strong>:</p><ul><li><p>&#8220;Verify an email identity in SES.&#8221;</p></li><li><p>&#8220;List domains managed by Route53.&#8221;</p></li><li><p>&#8220;Configure an S3 bucket policy.&#8221;</p></li></ul></li></ul><p><strong>Installation</strong>:</p><p>sudo apt update</p><p>sudo apt install awscli -y</p><p>aws configure # Follow prompts for Access Key ID, Secret Access Key, Region</p><h3>F. Bitwarden Secrets Manager (BWS)</h3><p>Your secrets should not live in text files or git. We use Bitwarden Secrets Manager (bws) to inject secrets directly into our environment.</p><ul><li><p><strong>Role</strong>: Secrets Vault.</p></li><li><p><strong>Superpower</strong>: Centralized, encrypted management of API keys and database credentials.</p></li><li><p><strong>When to use</strong>:</p><ul><li><p>&#8220;Inject the production database URL into the deployment script.&#8221;</p></li><li><p>&#8220;Rotate the Stripe API key across all services.&#8221;</p></li></ul></li></ul><p><strong>Installation</strong>:</p><p># Download from https://bitwarden.com/help/secrets-manager-cli/</p><p># Authenticate</p><p>bws login</p><h2>3. What is Obsidian?</h2><p>Obsidian is a local-first, Markdown-based knowledge management application that serves as the foundation for our Context System. Unlike cloud-based tools, your notes are stored as plain text files on your device, ensuring privacy, offline access, and no vendor lock-in.</p><p><strong>Key Features:</strong></p><ul><li><p><strong>Local Storage</strong>: All notes are Markdown files on your filesystem</p></li><li><p><strong>Bidirectional Links</strong>: [[Wiki-style links]] connect related notes</p></li><li><p><strong>Graph View</strong>: Visualizes connections between your notes</p></li><li><p><strong>Plugin Ecosystem</strong>: Thousands of community plugins extend functionality</p></li><li><p><strong>Cross-Platform</strong>: Available on macOS, Windows, Linux, iOS, and Android</p></li></ul><p><strong>Why Obsidian for Infrastructure?</strong></p><ol><li><p><strong>AI-Friendly</strong>: Plain text files can be read by any AI agent</p></li><li><p><strong>Version Control</strong>: Markdown files work perfectly with Git</p></li><li><p><strong>No Lock-in</strong>: Your data is yours forever</p></li><li><p><strong>Offline-First</strong>: Works without internet connection</p></li></ol><p><strong>Pricing</strong> (as of February 2025):</p><ul><li><p>Personal use: Free (including commercial use)</p></li><li><p>Sync service: Optional paid add-on</p></li><li><p>Publish service: Optional paid add-on</p></li></ul><p><strong>Installation</strong>: Download from <a href="https://obsidian.md">obsidian.md</a></p><h2>4. The &#8220;Context-First&#8221; Workflow with Obsidian</h2><p>The biggest mistake engineers make with AI is <strong>Ambiguity</strong>. If you type &#8220;Install Postgres,&#8221; the AI guesses. You want &#8220;Postgres 17 on Ubuntu 24.04 via Docker with specific tuning.&#8221;</p><p>To solve this, we use a <strong>Context System</strong> anchored in your <strong>Obsidian Vault</strong>.</p><h3>Step 1: Create Your Context Root</h3><p>Your Obsidian vault is the &#8220;Source of Truth.&#8221; Create a folder: 00 - Inbox/AI Context (or where you store project specs).</p><h3>Step 2: The Infrastructure Context File</h3><p>Create 00 - Inbox/AI Context/INFRASTRUCTURE_CONTEXT.md. This file is the &#8220;Bible.&#8221;</p><p><strong>Template:</strong></p><p># Project Infrastructure Context</p><p>## Status</p><p>- **Phase**: 1 (Foundation)</p><p>- **Last Updated**: {{date:YYYY-MM-DD}}</p><p>## Architecture Overview</p><p>We are building a &#8220;Safe Harbor&#8221; 2-node cluster.</p><p>- **Node 1 (Apps VPS)**: Public Gateway. Runs Coolify, Traefik, Next.js.</p><p>- **Node 2 (DB VPS)**: Private Vault. Runs Postgres, Redis, PgBouncer.</p><p>- **Network**: Tailscale Mesh (100.x range) for internal communication.</p><p>- **Edge**: Cloudflare (DNS, Proxy, R2).</p><p>- **Email**: AWS SES.</p><p>## Server Inventory</p><p>| Role | Hostname | Public IP | Tailscale IP | User | OS |</p><p>| :--- | :--- | :--- | :--- | :--- | :--- |</p><p>| App Gateway | `apps-vps` | TBD | TBD | `deploy` | Ubuntu 24.04 |</p><p>| Database | `db-vps` | TBD | TBD | `deploy` | Ubuntu 24.04 |</p><p>## Credentials &amp; Access</p><p>- **Contabo**: (Link to [[Contabo Credentials]])</p><p>- **Tailscale**: (Link to [[Tailscale Admin]])</p><p>- **Cloudflare API Token**: (Link to [[Cloudflare Credentials]])</p><p>- **AWS Credentials**: (Link to [[AWS CLI Credentials]])</p><p>- **SSH Keys**: `~/.ssh/id_ed25519_vps`</p><p>## Constraints</p><p>1.  **Security**:</p><p>    - `db-vps` must NEVER have public ports open (except SSH via VPN).</p><p>    - Root login disabled.</p><p>    - SSH Key Auth only.</p><p>2.  **Tooling**:</p><p>    -   **Orchestration**: Coolify (Apps), Docker Compose (DB).</p><p>    -   **Scripting**: Bash for simple tasks, Python for complex logic.</p><p>    -   **DNS**: Managed via Cloudflare API (curl).</p><p>    -   **AWS Services**: Managed via `awscli`.</p><p>    - **Secrets**: Managed via `bws` CLI.</p><p>3.  **Paths**:</p><p>    - Persistent Data: `/opt/&lt;service&gt;/data`</p><p>    - Configs: `/opt/&lt;service&gt;/config`</p><p>    - Scripts: `/opt/&lt;service&gt;/scripts`</p><p>## Secrets Policy</p><p>- **NEVER store actual secrets in this file.**</p><p>- Use placeholders: `{{BITWARDEN_LOOKUP}}`.</p><p>- Store secrets in Bitwarden Secrets Manager. Use `bws` to retrieve them.</p><h3>Step 3: The &#8220;Prime&#8221; Command</h3><p>Before any session, you force the AI to read this file.</p><p><strong>With Claude Code</strong>:</p><p>claude --context &#8220;00 - Inbox/AI Context/INFRASTRUCTURE_CONTEXT.md&#8221; &#8220;SSH into apps-vps and...&#8221;</p><h2>5. Setting Up Local Control Center</h2><h3>A. SSH Config (~/.ssh/config)</h3><p>Your agents need this to connect without typing IPs.</p><p># Defaults</p><p>Host *</p><p>    User deploy</p><p>    IdentityFile ~/.ssh/id_ed25519</p><p>    ServerAliveInterval 60</p><p># The Apps Server</p><p>Host apps-vps</p><p>    HostName 144.126.x.x  # We will fill this later</p><p>    User deploy</p><p>    IdentityFile ~/.ssh/id_ed25519_vps</p><p># The Database Server (Via Tailscale)</p><p>Host db-vps</p><p>    HostName 100.x.y.z</p><p>    User deploy</p><p>    IdentityFile ~/.ssh/id_ed25519_vps</p><h3>B. Local Secrets</h3><p>Set API keys as environment variables, not text.</p><p>export CLOUDFLARE_API_TOKEN=&#8221;...&#8221;</p><p>export AWS_ACCESS_KEY_ID=&#8221;...&#8221;</p><p>export AWS_SECRET_ACCESS_KEY=&#8221;...&#8221; # For aws-cli</p><p>export AWS_DEFAULT_REGION=&#8221;us-east-1&#8221; # For aws-cli</p><h2>6. The Execution Loop</h2><p>For every module, follow this loop:</p><ol><li><p><strong>Intent (Gemini)</strong>: &#8220;I need to set up backups. What is the best practice for Postgres on Docker?&#8221;</p></li><li><p><strong>Plan (Claude)</strong>: &#8220;Create a plan to implement WAL-G. List scripts and cron jobs.&#8221;</p></li><li><p><strong>Review (Human)</strong>: Check against constraints. &#8220;Wait, use R2, not S3.&#8221;</p></li><li><p><strong>Execute (Claude)</strong>: &#8220;SSH into db-vps and run the plan.&#8221;</p></li><li><p><strong>Verify (Codex/Obsidian)</strong>: &#8220;Update INFRASTRUCTURE_CONTEXT.md with the backup schedule.&#8221;</p></li></ol><div><hr></div><p><strong>Next Step</strong>: We have the tools. Now let&#8217;s understand the mission. Go to <a href="https://open.substack.com/pub/cvsloane/p/the-case-for-self-hosting?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">02. The Case for Self-Hosting</a>.</p>]]></content:encoded></item><item><title><![CDATA[Escape the Cloud: The AI-Native Infrastructure Masterclass]]></title><description><![CDATA[Stop paying the &#8220;Success Tax.&#8221; Start acting like a Systems Architect.]]></description><link>https://cvsloane.substack.com/p/escape-the-cloud-the-ai-native-infrastructure</link><guid isPermaLink="false">https://cvsloane.substack.com/p/escape-the-cloud-the-ai-native-infrastructure</guid><dc:creator><![CDATA[Chris Sloane]]></dc:creator><pubDate>Fri, 21 Nov 2025 18:56:31 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3d8e930b-220f-45d4-a1fe-a2ee9d22cf49_2816x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zh6l!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zh6l!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zh6l!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zh6l!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zh6l!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zh6l!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg" width="1456" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:770700,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://cvsloane.substack.com/i/179579615?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zh6l!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!zh6l!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!zh6l!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!zh6l!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c49d5ff-9d8f-4b35-9b3c-3d561591061a_2816x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you are building software today, you probably started on Vercel or Supabase. And why wouldn&#8217;t you? They are miraculous. You push code, and it&#8217;s live. You click a button, and you have a database. It feels like magic.</p><p>Until the bill arrives.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://cvsloane.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Chris's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>The pricing models of managed cloud services are designed to capture your upside. They are a &#8220;Success Tax.&#8221; A viral post, a DDOS attack, or simply a slightly inefficient database query can turn your $20/month hobby bill into a $500/month crisis.</p><p>But the cost isn&#8217;t the only problem. It&#8217;s the <strong>control</strong>. When your API is slow on Vercel, you are debugging a black box. You cannot see the CPU steal time. You cannot see the neighbor process on the Lambda host. You are a tenant, not an owner.</p><h3>The &#8220;Hard&#8221; Way is Now the Easy Way</h3><p>Historically, the alternative&#8212;managing your own Linux servers (VPS)&#8212;was reserved for bearded sysadmins who memorized iptables syntax and enjoyed suffering.</p><p><strong>Artificial Intelligence has changed this equation.</strong></p><p>Tools like <strong>Claude Code</strong>, <strong>Gemini</strong>, and <strong>Codex</strong> have turned the Command Line Interface (CLI) into a natural language conversation. You no longer need to memorize how to write a systemd unit file. You simply need to <em>intent</em> it.</p><p>This series is a <strong>Masterclass</strong>. It is a step-by-step blueprint to take you from a &#8220;Vercel Consumer&#8221; to an &#8220;AI-Native Systems Architect.&#8221;</p><p>We will build a production-grade infrastructure on commodity hardware (Contabo/Hetzner) that rivals the reliability of AWS, for 1/10th the cost.</p><div><hr></div><h2>The Curriculum</h2><p>This series is broken into 14 in-depth guides. Each one includes the theory, the execution steps, and the exact <strong>AI Prompts</strong> you need to copy-paste to get the job done.</p><h3>Phase 1: The Foundation</h3><p><strong><a href="https://open.substack.com/pub/cvsloane/p/the-ai-native-ops-workflow?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">01. The AI-Native Ops Workflow</a></strong> We stop typing commands manually. We set up the &#8220;Triad&#8221; of AI agents (Codex, Claude, Gemini) and build an Obsidian-based &#8220;Context System&#8221; that serves as the brain of our infrastructure.</p><p><strong><a href="https://open.substack.com/pub/cvsloane/p/the-case-for-self-hosting?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">02. The Case for Self-Hosting</a></strong> A rigorous financial and operational analysis. We calculate the ROI of leaving the cloud ($6,000/year savings for a moderate app) and define our &#8220;Safe Harbor&#8221; 2-node architecture.</p><p><strong><a href="https://open.substack.com/pub/cvsloane/p/the-metal-foundation?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">03. The Metal Foundation</a></strong> We go shopping for bare metal. I guide you through purchasing high-performance NVMe VPSs and performing the &#8220;First Boot&#8221; ritual using AI to secure SSH and harden the OS.</p><p><strong><a href="https://open.substack.com/pub/cvsloane/p/the-private-mesh?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">04. The Private Mesh</a></strong> Connecting our isolated servers using <strong>Tailscale</strong>. We implement a Zero-Trust mesh network that allows our servers to talk securely without ever opening a database port to the public internet.</p><h3>Phase 2: The Platform</h3><p><strong><a href="https://open.substack.com/pub/cvsloane/p/the-app-engine">05. The App Engine</a></strong> Turning a raw Linux server into a PaaS. We install <strong>Docker</strong> and <strong>Coolify</strong>, effectively giving you your own private Vercel dashboard for a fraction of the price.</p><p><strong><a href="https://open.substack.com/pub/cvsloane/p/observability-stack?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">06. Observability Stack</a></strong> Flying blind is dangerous. We deploy a &#8220;Survival&#8221; monitoring stack (Prometheus, Grafana, Uptime Kuma) that lives outside our platform, ensuring we get alerted via AWS SES even if everything else fails.</p><p><strong><a href="https://open.substack.com/pub/cvsloane/p/the-edge-layer?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">07. The Edge Layer</a></strong> Configuring <strong>Cloudflare</strong> as our global shield. We use the wrangler CLI to manage DNS as code, implement &#8220;Full (Strict)&#8221; SSL, and set up HSTS for banking-grade security.</p><h3>Phase 3: The Database</h3><p><strong><a href="https://open.substack.com/pub/cvsloane/p/high-performance-database?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">08. High-Performance Database</a></strong> The crown jewels. We deploy a dedicated Postgres 16 node, tune it for our specific hardware using AI calculations, and implement <strong>PgBouncer</strong> to handle thousands of concurrent connections.</p><p><strong><a href="https://open.substack.com/pub/cvsloane/p/data-durability-strategy?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">09. Data Durability Strategy</a></strong> Backups are nothing without restoration. We script automated nightly dumps to <strong>Cloudflare R2</strong> and create an automated <strong>&#8220;Restore Drill&#8221;</strong> that verifies our backups actually work every single month.</p><h3>Phase 4: The Application</h3><p><strong><a href="https://open.substack.com/pub/cvsloane/p/containerizing-nextjs?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">10. Containerizing Next.js</a></strong> A deep dive into Docker optimization. We use Multi-Stage builds and output: standalone to shrink our Next.js application image from 1GB to &lt;100MB.</p><p><strong><a href="https://open.substack.com/pub/cvsloane/p/the-great-data-migration?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">11. The Great Data Migration</a></strong> The scary part. We script the surgical extraction of data from Supabase (including the Auth schema), sanitize it, and hydrate our new Postgres instance without losing a single user.</p><p><strong><a href="https://open.substack.com/pub/cvsloane/p/production-configuration?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">12. Production Configuration</a></strong> Re-wiring the application for its new home. We manage secrets securely, fix OAuth callbacks for Google/GitHub, and deploy persistent <strong>Background Workers</strong> (Redis/BullMQ) to handle heavy tasks.</p><h3>Phase 5: Operations</h3><p><strong><a href="https://open.substack.com/pub/cvsloane/p/the-cutover-event?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">13. The Cutover Event</a></strong> The Go-Live runbook. We script a zero-downtime DNS switchover using Cloudflare, ensure email deliverability (DKIM/SPF), and flip the switch.</p><p><strong><a href="https://open.substack.com/pub/cvsloane/p/day-2-operations?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">14. Day 2 Operations</a></strong> Living with the machine. We cover log analysis using Gemini&#8217;s massive context window, cost tracking, and the &#8220;Break Glass&#8221; emergency procedures you need when the pager goes off.</p><div><hr></div><p><strong>Are you ready to own your infrastructure?</strong></p><p><a href="https://open.substack.com/pub/cvsloane/p/the-ai-native-ops-workflow?r=164a0x&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">Start with Part 1: The AI-Native Ops Workflow</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://cvsloane.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Chris's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>