<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>SQP Dev</title>
    <link>/</link>
    <description>Recent content on SQP Dev</description>
    <generator>Hugo</generator>
    <language>en-gb</language>
    <lastBuildDate>Thu, 28 May 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Pushing the Vault to TickTick — Closing the Loop Between Obsidian and Task Management</title>
      <link>/posts/2026-05-28-obsidian-ticktick-sync/</link>
      <pubDate>Thu, 28 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-28-obsidian-ticktick-sync/</guid>
      <description>&lt;p&gt;The vault knows everything. It has the lesson schedule, the practice sessions, the quiz material. TickTick is where the day actually runs — it sits in the calendar, it sends reminders, it blocks time. For a while those two lived separately, and any reschedule meant updating both by hand.&lt;/p&gt;&#xA;&lt;p&gt;Today that gap is closed.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-problem&#34;&gt;The Problem&lt;/h2&gt;&#xA;&lt;p&gt;Obsidian is the source of truth for the learning plan. But TickTick is what I actually look at during the day. When a lesson moved, I&amp;rsquo;d update the calendar note in Obsidian and then forget to touch TickTick. Or update TickTick and leave the vault behind. Either way, something was always out of sync.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Building an AI-Powered Quiz Into the Learning Loop</title>
      <link>/posts/2026-05-27-ai-quiz-learning-loop/</link>
      <pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-27-ai-quiz-learning-loop/</guid>
      <description>&lt;p&gt;Answering questions in your own words is good for learning. Answering them by clicking a button is better than not answering them at all.&lt;/p&gt;&#xA;&lt;p&gt;That sounds like a low bar, but it&amp;rsquo;s actually the insight that shaped the quiz system. At 20:00 after a full day I&amp;rsquo;m not going to write paragraphs about container networking from memory. But I will click through ten multiple choice questions if the friction is low enough and the questions are good.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Reclaim, Google Calendar, and the Morning Briefing Skill</title>
      <link>/posts/2026-05-26-reclaim-morning-briefing/</link>
      <pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-26-reclaim-morning-briefing/</guid>
      <description>&lt;p&gt;The moment that changed the whole system was realising that Claude Code is a terrible calendar manager.&lt;/p&gt;&#xA;&lt;p&gt;Not because it can&amp;rsquo;t do it — it can. The Google Calendar MCP integration works, skills can create and update events, everything is technically possible. The problem is cost. Every calendar operation burns tokens. Reschedule five tasks and you&amp;rsquo;ve spent more on API calls than the Reclaim monthly subscription.&lt;/p&gt;&#xA;&lt;p&gt;So I made a clean decision: Claude Code never touches the calendar for scheduling. That&amp;rsquo;s Reclaim&amp;rsquo;s job.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Structuring the Vault Around a Real Learning Plan</title>
      <link>/posts/2026-05-25-vault-structure-learning-plan/</link>
      <pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-25-vault-structure-learning-plan/</guid>
      <description>&lt;p&gt;Once the task manager question was settled, I could focus on what the vault actually needed to contain.&lt;/p&gt;&#xA;&lt;p&gt;The goal was simple: every Docker and Kubernetes lesson should have a home in the vault before I watch it, a note created during it, and a review scheduled after it. No manual steps, no decisions to make in the moment.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-learning-plan-structure&#34;&gt;The Learning Plan Structure&lt;/h2&gt;&#xA;&lt;p&gt;The Udemy course has 26 lessons spread across Docker and Kubernetes. I mapped them to a 5-week calendar: two sessions per day this week at 09:30 and 11:00, then one session per day at 06:35 from June onwards when I shift to an earlier schedule.&lt;/p&gt;</description>
    </item>
    <item>
      <title>TickTick, Todoist, Reclaim — Finding the Right Task Manager</title>
      <link>/posts/2026-05-24-ticktick-todoist-reclaim/</link>
      <pubDate>Sun, 24 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-24-ticktick-todoist-reclaim/</guid>
      <description>&lt;p&gt;Every learning system needs somewhere tasks live. I went through three tools in fairly quick succession, and each one taught me something useful.&lt;/p&gt;&#xA;&lt;h2 id=&#34;ticktick--the-comfortable-choice&#34;&gt;TickTick — The Comfortable Choice&lt;/h2&gt;&#xA;&lt;p&gt;I started with TickTick because I already knew it. Simple interface, good mobile app, doesn&amp;rsquo;t require an hour of setup to get started. For general life tasks it&amp;rsquo;s perfectly good.&lt;/p&gt;&#xA;&lt;p&gt;The problem was the edges. TickTick doesn&amp;rsquo;t connect naturally to Obsidian. There&amp;rsquo;s no clean way to link a task in TickTick to a note in Obsidian, and no way to query TickTick tasks from inside the vault. Every time I finished a lesson and wanted to log it, I was switching apps, copying information, doing the same thing twice.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Claude Code as a Vault Brain — First Steps</title>
      <link>/posts/2026-05-23-claude-code-vault-brain/</link>
      <pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-23-claude-code-vault-brain/</guid>
      <description>&lt;p&gt;The DevOps Learning Command Center dashboard existed. The problem was keeping it alive.&lt;/p&gt;&#xA;&lt;p&gt;A learning system that requires manual maintenance eventually stops being maintained. I wanted Claude Code — the terminal-based AI agent from Anthropic — to handle the vault upkeep so I could focus on the actual learning.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-claude-code-does&#34;&gt;What Claude Code Does&lt;/h2&gt;&#xA;&lt;p&gt;Claude Code runs in your terminal and has direct access to your filesystem. It reads files, writes files, runs commands. Combined with a &lt;code&gt;CLAUDE.md&lt;/code&gt; context file that explains who you are and what the vault is for, it becomes something like a personal assistant that actually knows your project.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Building a DevOps DevOps Learning Command Center in Obsidian</title>
      <link>/posts/2026-05-22-obsidian-command-center/</link>
      <pubDate>Fri, 22 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-22-obsidian-command-center/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m 56, learning DevOps from scratch, and I have a problem with tools.&lt;/p&gt;&#xA;&lt;p&gt;Not using them — I love tools. The problem is spending more time building the perfect learning environment than actually learning. It&amp;rsquo;s a trap I know well and fall into anyway.&lt;/p&gt;&#xA;&lt;p&gt;This time I decided to be deliberate about it. If I was going to build a system, it would be one that genuinely serves the learning — not just looks good in screenshots.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Agentic AI on Bitfrost — YouTube Playlists on Demand</title>
      <link>/posts/2026-05-20-n8n-agentic-playlists/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-20-n8n-agentic-playlists/</guid>
      <description>&lt;p&gt;And now for something completely different.&lt;/p&gt;&#xA;&lt;p&gt;Last time it was monitoring — metrics, dashboards, alerts. Serious infrastructure business. This time I wanted to see what happens when you put an AI agent on &lt;strong&gt;Bitfrost&lt;/strong&gt; and give it something genuinely frivolous to do: build YouTube playlists.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-idea&#34;&gt;The Idea&lt;/h2&gt;&#xA;&lt;p&gt;The workflow is simple. You type one line:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Docker Basics | Docker tutorial for beginners&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Left of the pipe is the playlist name. Right is the search topic. Hit enter. A few seconds later a private YouTube playlist exists, populated with real videos, curated by Claude.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Building a Homelab Monitoring Stack from Scratch</title>
      <link>/posts/2026-05-19-homelab-monitoring/</link>
      <pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-19-homelab-monitoring/</guid>
      <description>&lt;p&gt;If you run a home server, you probably have a rough idea of whether things are working — you can SSH in, run &lt;code&gt;top&lt;/code&gt;, check disk space, maybe peek at some logs. But that&amp;rsquo;s reactive. You only look when something feels wrong. Proper monitoring means you know before something breaks, or at the very least, you know the moment it does.&lt;/p&gt;&#xA;&lt;p&gt;This is the story of how I set up a full observability stack on my home server, &lt;strong&gt;bitfrost&lt;/strong&gt; — an Ubuntu 24.04 machine running several Docker-based apps.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Helena &amp; Teodor&#39;s Game Lab — A Family Side Project</title>
      <link>/posts/2026-05-16-gamelab-blog-entry/</link>
      <pubDate>Sat, 16 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-16-gamelab-blog-entry/</guid>
      <description>&lt;p&gt;Sometimes the best projects start with a simple idea: &lt;em&gt;what if the kids could make their own games?&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s how &lt;strong&gt;Helena &amp;amp; Teodor&amp;rsquo;s Game Lab&lt;/strong&gt; came about — a small website where my children, Helena and Teodor, get to have their own collection of browser games. The concept is straightforward: a colourful landing page that showcases each game, and clicking &amp;ldquo;Play&amp;rdquo; takes you straight in. No downloads, no installs, just open and play.&lt;/p&gt;</description>
    </item>
    <item>
      <title>SQP Kanban — Containerising a Node.js App with Docker</title>
      <link>/posts/2026-05-14-blog-sqp-kanban-project-overview/</link>
      <pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-14-blog-sqp-kanban-project-overview/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been building a simple kanban board called SQP Kanban — a Node.js/Express app backed by MongoDB. The app itself was functional, but it was running bare on my machine with no containerisation, no CI/CD, and no deployment pipeline. Time to fix that.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-app&#34;&gt;The App&lt;/h2&gt;&#xA;&lt;p&gt;SQP Kanban is a drag-and-drop project board with columns (To Do, In Progress, Completed, Omit), task cards with date tracking, and colour-coded projects. The frontend is vanilla HTML/JS, the backend is Express with Mongoose, and data lives in MongoDB.&lt;/p&gt;</description>
    </item>
    <item>
      <title>SQP Kanban — The Deployment Process</title>
      <link>/posts/2026-05-14-blog-sqp-kanban-deployment-process/</link>
      <pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-14-blog-sqp-kanban-deployment-process/</guid>
      <description>&lt;p&gt;This is the second post about containerising SQP Kanban. Here&amp;rsquo;s how the actual process went.&lt;/p&gt;&#xA;&lt;h2 id=&#34;step-1--dockerfile&#34;&gt;Step 1 — Dockerfile&lt;/h2&gt;&#xA;&lt;p&gt;Straightforward. Alpine-based Node image, copy &lt;code&gt;package.json&lt;/code&gt; first (so &lt;code&gt;npm install&lt;/code&gt; gets cached), then copy the app code. The main lesson was getting the layer order right for efficient rebuilds.&lt;/p&gt;&#xA;&lt;h2 id=&#34;step-2--manual-docker-run&#34;&gt;Step 2 — Manual Docker Run&lt;/h2&gt;&#xA;&lt;p&gt;Before Docker Compose, I ran everything manually to understand what each piece does:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker network create sqp-network&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker run -d --name sqp-mongo --network sqp-network -v sqp-mongo-data:/data/db mongo:8&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker build -t sqp-kanban .&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker run -d --name sqp-kanban --network sqp-network -p 4000:4000 -e MONGO_URI&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;mongodb://sqp-mongo:27017/sqp-kanban sqp-kanban&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Four commands. Each one taught me something — networks, volumes, port mapping, environment variables. Worth doing manually at least once before reaching for Compose.&lt;/p&gt;</description>
    </item>
    <item>
      <title>SQP Kanban — Three Bugs That Broke Deployment</title>
      <link>/posts/2026-05-14-blog-sqp-kanban-bugs-and-debugging/</link>
      <pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-14-blog-sqp-kanban-bugs-and-debugging/</guid>
      <description>&lt;p&gt;Deploying SQP Kanban looked simple on paper. In practice, three bugs made it interesting.&lt;/p&gt;&#xA;&lt;h2 id=&#34;bug-1--mongoose-refuses-to-save&#34;&gt;Bug 1 — Mongoose Refuses to Save&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Adding projects and tasks worked in the UI, but nothing persisted after a page refresh. The save status was stuck on &amp;ldquo;Saving&amp;hellip;&amp;rdquo; forever.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Two problems hiding as one.&lt;/strong&gt; First, the save status update in &lt;code&gt;saveBoard()&lt;/code&gt; was a bare template literal that didn&amp;rsquo;t actually call anything:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// This does nothing — it&amp;#39;s just a floating string expression&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`Saved &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Date().&lt;span style=&#34;color:#a6e22e&#34;&gt;toLocaleTimeString&lt;/span&gt;()&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Second — and this was the real blocker — the Mongoose schemas for subdocuments (projects, tasks, columns) used the default &lt;code&gt;_id&lt;/code&gt; type of &lt;code&gt;ObjectId&lt;/code&gt;, but the frontend was generating IDs with &lt;code&gt;crypto.randomUUID()&lt;/code&gt;, which produces UUIDs like &lt;code&gt;b14a56c5-2628-4138-990c-cb685fdf04ce&lt;/code&gt;. MongoDB ObjectIds are 24-character hex strings. Mongoose threw a &lt;code&gt;CastError&lt;/code&gt; on every save attempt.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Adding HTTPS with Caddy and DuckDNS</title>
      <link>/posts/2026-05-10-adding-https-with-caddy-and-duckdns/</link>
      <pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-10-adding-https-with-caddy-and-duckdns/</guid>
      <description>&lt;h1 id=&#34;adding-https-with-caddy-and-duckdns&#34;&gt;Adding HTTPS with Caddy and DuckDNS&lt;/h1&gt;&#xA;&lt;p&gt;After deploying the application to EC2, it initially worked only through:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;http://EC2-IP&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The next step was understanding how HTTPS is normally added.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-stack&#34;&gt;The Stack&lt;/h2&gt;&#xA;&lt;p&gt;I used:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;DuckDNS for a free test domain&lt;/li&gt;&#xA;&lt;li&gt;Caddy as a reverse proxy&lt;/li&gt;&#xA;&lt;li&gt;Let&amp;rsquo;s Encrypt certificates managed automatically by Caddy&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Final setup:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;https://dsuopd.duckdns.org&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;architecture&#34;&gt;Architecture&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Internet&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   ↓&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Caddy&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   ↓&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;localhost:3000&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   ↓&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Docker app container&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The application container itself is no longer publicly exposed directly.&lt;/p&gt;</description>
    </item>
    <item>
      <title>First Real Cloud Deployment</title>
      <link>/posts/2026-05-10-first-real-cloud-deployment/</link>
      <pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-10-first-real-cloud-deployment/</guid>
      <description>&lt;h1 id=&#34;first-real-cloud-deployment&#34;&gt;First Real Cloud Deployment&lt;/h1&gt;&#xA;&lt;p&gt;Today I deployed my first real full-stack application to AWS EC2.&lt;/p&gt;&#xA;&lt;p&gt;The project is a hospital staff allocation system built with:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;React + Vite frontend&lt;/li&gt;&#xA;&lt;li&gt;Express backend&lt;/li&gt;&#xA;&lt;li&gt;PostgreSQL database&lt;/li&gt;&#xA;&lt;li&gt;Docker containers&lt;/li&gt;&#xA;&lt;li&gt;Docker Compose orchestration&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;what-i-learned&#34;&gt;What I Learned&lt;/h2&gt;&#xA;&lt;p&gt;The most important thing was understanding how the different layers connect together.&lt;/p&gt;&#xA;&lt;p&gt;Final architecture:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Browser&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   ↓ HTTPS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DuckDNS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   ↓&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Caddy reverse proxy&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   ↓&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Docker app container&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   ↓&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PostgreSQL container&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;deployment-workflow&#34;&gt;Deployment Workflow&lt;/h2&gt;&#xA;&lt;p&gt;The workflow looked like this:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Understanding Docker Volumes and PostgreSQL</title>
      <link>/posts/2026-05-10-understanding-docker-volumes-and-postgres/</link>
      <pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-10-understanding-docker-volumes-and-postgres/</guid>
      <description>&lt;h1 id=&#34;understanding-docker-volumes-and-postgresql&#34;&gt;Understanding Docker Volumes and PostgreSQL&lt;/h1&gt;&#xA;&lt;p&gt;One of the most confusing parts of my first deployment was PostgreSQL authentication failing even after changing the password in &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The important lesson:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PostgreSQL only uses POSTGRES_PASSWORD the first time the database is created.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;what-happened&#34;&gt;What Happened&lt;/h2&gt;&#xA;&lt;p&gt;I originally started the database container with password A.&lt;/p&gt;&#xA;&lt;p&gt;Docker created a persistent volume:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dsu_pg_data&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Later I changed the password in &lt;code&gt;.env&lt;/code&gt; to password B.&lt;/p&gt;&#xA;&lt;p&gt;The application then failed with:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Docker Basics — Building and Deploying to EC2</title>
      <link>/posts/2026-05-08-docker-basics-ec2/</link>
      <pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-08-docker-basics-ec2/</guid>
      <description>&lt;p&gt;Docker packages an application and everything it needs into a container. Same image, identical behaviour on any machine. This post covers building a custom nginx image and deploying it to AWS EC2.&lt;/p&gt;&#xA;&lt;h2 id=&#34;podman-on-fedora&#34;&gt;Podman on Fedora&lt;/h2&gt;&#xA;&lt;p&gt;Docker doesn&amp;rsquo;t run inside a Toolbox container — kernel module restrictions. Podman is Docker-compatible and works natively on Fedora:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;alias docker=podman&amp;#39;&lt;/span&gt; &amp;gt;&amp;gt; ~/.bashrc&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;source ~/.bashrc&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Same commands, no workarounds needed from this point.&lt;/p&gt;&#xA;&lt;h2 id=&#34;building-a-custom-image&#34;&gt;Building a Custom Image&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker-nginx/&#xA;├── Dockerfile&#xA;└── index.html&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-dockerfile&#34; data-lang=&#34;dockerfile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; docker.io/library/nginx:latest&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;COPY&lt;/span&gt; index.html /usr/share/nginx/html/&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;EXPOSE&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; 80&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker build -t my-nginx:v1.0 .&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker run -d -p 8080:80 --name my-nginx my-nginx:v1.0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Open &lt;code&gt;http://localhost:8080&lt;/code&gt; — custom HTML served via nginx inside a container.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Automating Terraform with GitHub Actions</title>
      <link>/posts/2026-05-07-github-actions-terraform/</link>
      <pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-07-github-actions-terraform/</guid>
      <description>&lt;p&gt;Manually running &lt;code&gt;terraform apply&lt;/code&gt; from a laptop works for learning. In the real world, infrastructure changes go through a pipeline. This is how I set one up.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-goal&#34;&gt;The Goal&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Every &lt;strong&gt;Pull Request&lt;/strong&gt; → run &lt;code&gt;terraform plan&lt;/code&gt; automatically&lt;/li&gt;&#xA;&lt;li&gt;Every &lt;strong&gt;merge to main&lt;/strong&gt; → run &lt;code&gt;terraform apply&lt;/code&gt; automatically&lt;/li&gt;&#xA;&lt;li&gt;AWS credentials stored securely as GitHub Secrets — never in code&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;workflow-file&#34;&gt;Workflow File&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Terraform CI/CD&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;on&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;push&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;branches&lt;/span&gt;: [&lt;span style=&#34;color:#ae81ff&#34;&gt;main]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;pull_request&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;branches&lt;/span&gt;: [&lt;span style=&#34;color:#ae81ff&#34;&gt;main]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;jobs&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;terraform&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;runs-on&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;ubuntu-latest&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;defaults&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;working-directory&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;exercise-02-variables-outputs&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;actions/checkout@v3&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;hashicorp/setup-terraform@v2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;uses&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;aws-actions/configure-aws-credentials@v2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;aws-access-key-id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;aws-secret-access-key&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;aws-region&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;eu-west-2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;terraform init&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;terraform fmt -check&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;terraform validate&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;terraform plan&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;if&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;github.event_name == &amp;#39;pull_request&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;terraform apply -auto-approve&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;if&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;github.ref == &amp;#39;refs/heads/main&amp;#39; &amp;amp;&amp;amp; github.event_name == &amp;#39;push&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;problems-i-hit&#34;&gt;Problems I Hit&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;Workflow not showing in Actions tab&lt;/strong&gt; — GitHub only reads workflow files from the &lt;code&gt;main&lt;/code&gt; branch. The file needs to be merged before it&amp;rsquo;s recognised.&lt;/p&gt;</description>
    </item>
    <item>
      <title>First Terraform Deployment on AWS</title>
      <link>/posts/2026-05-05-first-terraform-deployment/</link>
      <pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-05-first-terraform-deployment/</guid>
      <description>&lt;p&gt;With the environment set up, I wrote my first real Terraform code — a complete VPC with a public subnet, security group, and an EC2 instance running nginx.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-i-built&#34;&gt;What I Built&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;VPC (&lt;code&gt;10.0.0.0/16&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;Public subnet with Internet Gateway&lt;/li&gt;&#xA;&lt;li&gt;Security group (SSH + HTTP)&lt;/li&gt;&#xA;&lt;li&gt;EC2 instance (Ubuntu 24.04)&lt;/li&gt;&#xA;&lt;li&gt;SSH key pair&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;file-structure&#34;&gt;File Structure&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;exercise-02-variables-outputs/&#xA;├── main.tf&#xA;├── variables.tf&#xA;├── locals.tf&#xA;├── outputs.tf&#xA;├── data.tf&#xA;├── key.tf&#xA;└── terraform.tfvars&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Separating resources into logical files keeps things clean and readable.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Setting Up a DevOps Environment on Fedora 42</title>
      <link>/posts/2026-05-01-devops-environment-fedora-42/</link>
      <pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026-05-01-devops-environment-fedora-42/</guid>
      <description>&lt;p&gt;Coming from a VFX and IT background, I decided to transition into DevOps. This is the first post in a series documenting that journey.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-fedora&#34;&gt;Why Fedora?&lt;/h2&gt;&#xA;&lt;p&gt;Fedora aligns well with enterprise DevOps — the same package manager (DNF), SELinux by default, and close ties to RHEL. The quirks you solve on Fedora build exactly the troubleshooting mindset DevOps engineers need.&lt;/p&gt;&#xA;&lt;h2 id=&#34;toolbox-for-isolation&#34;&gt;Toolbox for Isolation&lt;/h2&gt;&#xA;&lt;p&gt;Rather than installing everything directly on the host, I use &lt;a href=&#34;https://containertoolbx.org/&#34;&gt;Toolbox&lt;/a&gt; — a container-based environment built on Podman. My DevOps tools live in a container called &lt;code&gt;sandbox&lt;/code&gt;, keeping the host clean.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
