<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Docker on SQP Dev</title>
    <link>/tags/docker/</link>
    <description>Recent content in Docker on SQP Dev</description>
    <generator>Hugo</generator>
    <language>en-gb</language>
    <lastBuildDate>Wed, 27 May 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="/tags/docker/index.xml" rel="self" type="application/rss+xml" />
    <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>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>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>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>
  </channel>
</rss>
