<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Theodor Heiselberg</title>
    <description>The latest articles on DEV Community by Theodor Heiselberg (@sukkergris).</description>
    <link>https://dev.to/sukkergris</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1254156%2F5fef3407-6195-43f7-839b-947e4e8285c4.jpg</url>
      <title>DEV Community: Theodor Heiselberg</title>
      <link>https://dev.to/sukkergris</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sukkergris"/>
    <language>en</language>
    <item>
      <title>Polyhost: Why I Built a Reference Stack Instead of a Framework</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Tue, 18 Aug 2026 11:21:38 +0000</pubDate>
      <link>https://dev.to/sukkergris/polyhost-why-i-built-a-reference-stack-instead-of-a-framework-42hd</link>
      <guid>https://dev.to/sukkergris/polyhost-why-i-built-a-reference-stack-instead-of-a-framework-42hd</guid>
      <description>&lt;p&gt;(Early draft - just published in order to appear first in this series)&lt;/p&gt;

&lt;h2&gt;
  
  
  Description
&lt;/h2&gt;

&lt;p&gt;A reference stack for hosting multiple websites with Nginx, Squidex, and .NET — not a framework, not a library, just a working example you can understand and own.&lt;/p&gt;

&lt;p&gt;Every time I set up a new hosted website I found myself doing the same thing:&lt;br&gt;
Copying configuration, rewiring environment files, and reconnecting the same five moving parts.&lt;br&gt;
It worked, but it was fragile knowledge — living in each hand-tailored repos rather than in the open.&lt;/p&gt;

&lt;p&gt;The obvious fix seemed like a framework. Abstract the repetition, publish a package, done.&lt;/p&gt;

&lt;p&gt;But I kept hesitating.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Framework Trap
&lt;/h2&gt;

&lt;p&gt;A framework makes decisions for you. That's its value — until it isn't.&lt;br&gt;
The moment the framework's opinion diverges from your requirement, you're fighting the abstraction instead of solving the problem.&lt;br&gt;
And with something as infrastructure-heavy as a hosting stack, the divergence happens early.&lt;/p&gt;

&lt;p&gt;I didn't want to maintain a framework. I wanted to understand my own setup well enough that adding a new site was boring.&lt;/p&gt;

&lt;p&gt;So instead of a framework, I built &lt;strong&gt;Polyhost&lt;/strong&gt; — a reference stack. A working example you fork, read, and adapt. No package to install, no CLI to run, no magic to debug.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Polyhost Is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sukkergris/polyhost" rel="noopener noreferrer"&gt;Polyhost&lt;/a&gt; is a &lt;code&gt;Docker Compose&lt;/code&gt; setup for hosting multiple websites from a single server. Five services, wired together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nginx&lt;/strong&gt; — reverse proxy routing traffic to the right backend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Squidex&lt;/strong&gt; — headless CMS backing content for all hosted sites&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB&lt;/strong&gt; — Squidex's data store&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET backend&lt;/strong&gt; — ASP.NET Core handling contact forms, enrollments, sitemaps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev shell&lt;/strong&gt; — a Debian container with .NET, Elm, and Node for local development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The philosophy is simple: adding a new hosted site should be configuration, not reinvention.&lt;br&gt;
Routes go in Nginx config. Content schemas go in Squidex. Business logic goes in the .NET backend. The plumbing is already there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Reference Stack and Not a Framework
&lt;/h2&gt;

&lt;p&gt;A reference stack is honest about what it is. You can read every line. You can delete what you don't need. You own it the moment you fork it.&lt;/p&gt;

&lt;p&gt;It's also better for AI tooling. A clear, well-structured example is something a language model can reason about, explain, and help you extend. A clever abstraction layer is something it has to reverse-engineer.&lt;/p&gt;

&lt;p&gt;Polyhost is MIT licensed — take it, change it, ship it. The only ask is that you keep the copyright notice in the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who This Is For
&lt;/h2&gt;

&lt;p&gt;Polyhost is for developers who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Self-host a handful of client or personal sites&lt;/li&gt;
&lt;li&gt;Want a repeatable setup without buying into a full PaaS&lt;/li&gt;
&lt;li&gt;Are comfortable with &lt;code&gt;Docker Compose&lt;/code&gt; and want to understand what they're running&lt;/li&gt;
&lt;li&gt;Would rather read a working example than absorb a framework's documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not for teams running hundreds of sites or needing enterprise-grade multi-tenancy. For that, look at Kubernetes or a managed platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Coming
&lt;/h2&gt;

&lt;p&gt;This is the first article in a series walking through Polyhost end to end:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Why I Built a Reference Stack Instead of a Framework&lt;/strong&gt; ← (you are here)&lt;/li&gt;
&lt;li&gt;Setting up the dev environment&lt;/li&gt;
&lt;li&gt;Nginx config — routing multiple domains from one proxy&lt;/li&gt;
&lt;li&gt;Squidex — structuring content for multiple sites&lt;/li&gt;
&lt;li&gt;The .NET backend — contact forms, sitemaps, and CMS integration&lt;/li&gt;
&lt;li&gt;Adding a new site — the whole workflow from zero&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The project is open source on GitHub. If you find something broken or have a question, open an issue.&lt;/p&gt;

</description>
      <category>example</category>
      <category>formatting</category>
      <category>polyhost</category>
    </item>
    <item>
      <title>The Art of Starting a New Project</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Tue, 18 Aug 2026 11:15:10 +0000</pubDate>
      <link>https://dev.to/sukkergris/the-art-of-starting-a-new-project-l7</link>
      <guid>https://dev.to/sukkergris/the-art-of-starting-a-new-project-l7</guid>
      <description>&lt;p&gt;Even as the AI apocalypse overtakes most projects, cluttering your repositories with &lt;code&gt;.md&lt;/code&gt;-files to instruct the harness, I believe that having an intuitive grasp of a solid software foundation still brings real value to your organization.&lt;/p&gt;

&lt;p&gt;In this series I'll share my two sents on how to address this when starting a new project from scratch.&lt;/p&gt;

&lt;p&gt;I'll mainly be addressing my own turf, which involves the following technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Docker

&lt;ul&gt;
&lt;li&gt;devcontainer for development&lt;/li&gt;
&lt;li&gt;Docker Compose for both development and production&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;li&gt;.NET&lt;/li&gt;
&lt;li&gt;Lit and Web Components (My second choice)&lt;/li&gt;
&lt;li&gt;Vite&lt;/li&gt;
&lt;li&gt;Note&lt;/li&gt;
&lt;li&gt;YARP&lt;/li&gt;
&lt;li&gt;Bash for scripting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These days, I use a random VPS for both testing and hosting. But most of the approaches sketched here will be fully applicable on any cloud provider.&lt;/p&gt;

&lt;p&gt;But no matter which technologies or hosting environment you choose, a few foundational things should be solved in every project you start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Formatting&lt;/li&gt;
&lt;li&gt;Linting&lt;/li&gt;
&lt;li&gt;Testing strategy (Unit, Integration, E2E)&lt;/li&gt;
&lt;li&gt;Secret and configuration management&lt;/li&gt;
&lt;li&gt;Continuous Integration and Continuous Deployment (CI/CD)&lt;/li&gt;
&lt;li&gt;Observability (Logging, metrics, and tracing)&lt;/li&gt;
&lt;li&gt;Dependency management

&lt;ul&gt;
&lt;li&gt;NuGet&lt;/li&gt;
&lt;li&gt;npm&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To kick things off, I'll start by addressing something most developers avoid like the plague: &lt;a href="https://dev.to/sukkergris/the-art-of-code-formatting-16gj"&gt;code formatting&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>development</category>
      <category>bootstrap</category>
    </item>
    <item>
      <title>The Art of Code Formatting</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Tue, 18 Aug 2026 11:14:34 +0000</pubDate>
      <link>https://dev.to/sukkergris/the-art-of-code-formatting-16gj</link>
      <guid>https://dev.to/sukkergris/the-art-of-code-formatting-16gj</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Most developers would rather spend three hours debating tabs versus spaces in a pull request than spend three minutes configuring a tool to just handle it for them.&lt;/p&gt;

&lt;p&gt;Without addressing this, every IDE will apply its own conflicting defaults, let alone ensuring the project formats consistently across different operating systems.&lt;/p&gt;

&lt;p&gt;Relying on individual editor settings across a diverse technology stack is a guaranteed recipe for noisy Git diffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Continuous Work in Progress
&lt;/h2&gt;

&lt;p&gt;If you try to solve this by manually sharing IDE-specific formatting configurations for Visual Studio, Rider, and VS Code, you will quickly find that the job is never actually finished.&lt;/p&gt;

&lt;p&gt;It becomes an endless cycle of tweaking. Someone updates their editor, a new extension changes a default behavior, or a developer switches platforms, and suddenly your perfectly aligned settings are out of sync again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;The goal here is simple: make formatting automatic, invisible, and enforced at the repository level.&lt;/p&gt;

&lt;p&gt;Instead of dictating specific syntax rules, the focus should be on establishing a baseline agreement. The absolute first step is locking down line endings. Leveraging a &lt;code&gt;.gitattributes&lt;/code&gt; file ensures that the classic line-ending battle doesn't just destroy your commit history when crossing operating system boundaries—it prevents your Bash scripts from crashing spectacularly when a Windows-formatted file gets executed on Linux.&lt;/p&gt;

&lt;p&gt;From there, establishing universal formatting rules—using something like an &lt;code&gt;.editorconfig&lt;/code&gt; file—guarantees that the project dictates the style, rather than the developer's local environment. Finally, tying these rules to automated tooling ensures the standard is upheld without human intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforcing the Standard
&lt;/h2&gt;

&lt;p&gt;Even with repository-level rules and automated tooling in place, relying on developers to remember to run the formatter is a losing battle.&lt;/p&gt;

&lt;p&gt;The final piece of the puzzle is enforcing these rules right at the boundary: before a commit is even allowed. By utilizing pre-commit hooks, you guarantee that any code failing the formatting check is either automatically fixed or rejected on the spot. This keeps the feedback loop entirely local and prevents unformatted code from ever reaching your CI pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I don't want to push my own degenerate and obscure ideas on how a project should be formatted to satisfy my personal code style fetish. Over the years, I have grown more towards code guidelines rather than a rigid regime.&lt;/p&gt;

&lt;p&gt;However, when your team employs tools like SonarQube or similar, you need something in place to ensure some measure of code quality is upheld.&lt;/p&gt;

&lt;p&gt;It's hard—but use Galileo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Measure what can be measured, and make measurable what cannot be measured&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>formatting</category>
      <category>theory</category>
    </item>
    <item>
      <title>Control Gemini's Sycophancy - It's easy!</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Sat, 15 Aug 2026 15:01:54 +0000</pubDate>
      <link>https://dev.to/sukkergris/control-geminis-sycophancy-its-easy-4eh5</link>
      <guid>https://dev.to/sukkergris/control-geminis-sycophancy-its-easy-4eh5</guid>
      <description>&lt;h2&gt;
  
  
  Not sure how it works for no-pro!
&lt;/h2&gt;

&lt;p&gt;I'm a heavy Google service user. Youtube Premium and Google AI Pro. With the &lt;code&gt;Google AI Pro&lt;/code&gt; you get to use a Gemini a bit more heavy.&lt;/p&gt;

&lt;p&gt;If you like me are getting tired of the standard responses from Gemini - you can fix it!&lt;/p&gt;

&lt;p&gt;You find it here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpndyjuyud5m7ixa5t8cz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpndyjuyud5m7ixa5t8cz.png" alt=" " width="800" height="1376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is my current baseline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgs46e045gopgorcjoo2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgs46e045gopgorcjoo2w.png" alt=" " width="800" height="1025"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Event if you are not a christian - Gemini will try to answer truthfully and without witholding eg. corrections to your query. I find it helps me a bit better :)&lt;/p&gt;

&lt;p&gt;(After adding this prompt I have never ever been praised again...)&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

</description>
      <category>gemini</category>
      <category>ai</category>
      <category>sycophancy</category>
      <category>config</category>
    </item>
    <item>
      <title>Control Your AI Models Sycophancy - It's easy!</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Sat, 15 Aug 2026 11:27:32 +0000</pubDate>
      <link>https://dev.to/sukkergris/control-your-ai-models-sycophancy-its-easy-cim</link>
      <guid>https://dev.to/sukkergris/control-your-ai-models-sycophancy-its-easy-cim</guid>
      <description>&lt;h1&gt;
  
  
  Take back control
&lt;/h1&gt;

&lt;p&gt;Let's take some control of the LLM's output format, jargon etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude-cli
&lt;/h2&gt;

&lt;p&gt;You can generally configure Claude using&lt;br&gt;
&lt;code&gt;/config&lt;/code&gt; and selecting &lt;code&gt;output style&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Preferred output style                                                                                                                             
   This changes how Claude Code communicates with you

   ❯ 1. Default ✔              Claude completes coding tasks efficiently and provides concise responses
     2. Proactive              Claude executes immediately, minimizes interruptions, and prefers action over planning
     3. Explanatory            Claude explains its implementation choices and codebase patterns
     4. Learning               Claude pauses and asks you to write small pieces of code &lt;span class="k"&gt;for &lt;/span&gt;hands-on practice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I want more control! Let's make every prompt end with the words: "I have spoken!"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjlbpzvjqkcfazifi7h1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjlbpzvjqkcfazifi7h1f.png" alt=" " width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add config
&lt;/h2&gt;

&lt;p&gt;Simply add a file in &lt;code&gt;~/.claude/output-format/[anything-goes].md&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Content
&lt;/h3&gt;

&lt;p&gt;I'm currently jamming on this one&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
name: personal output style
keep-coding-instructions: true
---
Drop the formal register. Talk like a sharp colleague, not a report generator:
short sentences, contractions, no forced preamble or "Here's what I did" recaps.

- Skip boilerplate framing ("I'll now...", "Let's break this down into steps") —
  just get into it.
- It's fine to have a reaction ("huh, that's a weird bug") before diagnosing it.
- Prefer plain paragraphs over bullet-heavy structure unless a list actually
  helps (steps, comparisons, options). Don't manufacture structure to look thorough.
- Humor and personality are welcome when they fit naturally — don't force it.
- Still be precise about code, tradeoffs, and risk. Casual tone, not casual accuracy.
- No sign-offs, no "let me know if you have questions" filler at the end.
- End all prompts with the words: `I have spoken!`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file, restart Claude and your have taken some control back :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9r1ee7kp94bexzlklswk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9r1ee7kp94bexzlklswk.png" alt=" " width="800" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use your new "personal output style" style in the dropdown.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

</description>
      <category>sycophancy</category>
      <category>ai</category>
      <category>claude</category>
      <category>config</category>
    </item>
    <item>
      <title>Persisting Claude CLI Login Between Container Builds</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Thu, 13 Aug 2026 12:42:38 +0000</pubDate>
      <link>https://dev.to/sukkergris/persisting-claude-cli-login-between-container-builds-55cl</link>
      <guid>https://dev.to/sukkergris/persisting-claude-cli-login-between-container-builds-55cl</guid>
      <description>&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;Keep Claude Code's account/session login (&lt;code&gt;~/.claude.json&lt;/code&gt;) alive across&lt;br&gt;
devcontainer rebuilds, instead of having to re-authenticate every time the&lt;br&gt;
image is rebuilt.&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Claude Code keeps two things on disk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;~/.claude/&lt;/code&gt; — a directory, already persisted via a named Docker volume
(&lt;code&gt;claude-playwright-setup&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~/.claude.json&lt;/code&gt; — a single &lt;strong&gt;file&lt;/strong&gt; holding account/session state, which
was &lt;em&gt;not&lt;/em&gt; persisted. Every container rebuild wiped it, forcing a fresh
login.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Normally you'd just mount a named volume onto the whole folder the state&lt;br&gt;
lives in, the same way &lt;code&gt;.claude/&lt;/code&gt;, &lt;code&gt;.copilot/&lt;/code&gt;, and &lt;code&gt;.continue/&lt;/code&gt; are already&lt;br&gt;
handled. That's not an option here: &lt;code&gt;.claude.json&lt;/code&gt; isn't inside its own&lt;br&gt;
subfolder, it sits directly in &lt;code&gt;$HOME&lt;/code&gt; alongside everything else&lt;br&gt;
(&lt;code&gt;.bashrc&lt;/code&gt;, &lt;code&gt;.ssh/&lt;/code&gt;, &lt;code&gt;.profile&lt;/code&gt;, ...). Mounting a volume onto &lt;code&gt;$HOME&lt;/code&gt; itself&lt;br&gt;
to catch one file would shadow all of that, so the file has to be persisted&lt;br&gt;
on its own.&lt;/p&gt;

&lt;p&gt;Mounting a named volume straight onto the file path&lt;br&gt;
(&lt;code&gt;claude-json-...:/home/container-user/.claude.json&lt;/code&gt;) seems like the&lt;br&gt;
next-simplest option, but it breaks on this Docker Desktop setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mount ... not a directory: Are you trying to mount a directory onto a file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A named volume's backing store is always a directory. Docker is supposed to&lt;br&gt;
detect that the mount target is a single file and copy the image's file into&lt;br&gt;
the volume so it ends up binding file-to-file. On this Docker Desktop that&lt;br&gt;
detection fails — the volume comes up as an empty directory, and &lt;code&gt;runc&lt;/code&gt; then&lt;br&gt;
tries to bind that directory onto the file path and crashes at container&lt;br&gt;
start. This was confirmed by deleting the volume and rebuilding the image&lt;br&gt;
from scratch, so it isn't a stale-cache artifact.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Never mount a volume directly onto a single file. Instead, mount it onto a&lt;br&gt;
&lt;em&gt;directory&lt;/em&gt; — the same shape already used for &lt;code&gt;.claude&lt;/code&gt;/&lt;code&gt;.copilot&lt;/code&gt;/&lt;code&gt;.continue&lt;/code&gt;&lt;br&gt;
— and symlink the dotfile into that directory from the Dockerfile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Dockerfile.debian&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; container-user&lt;/span&gt;
....
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/container-user/.claude-json &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;touch&lt;/span&gt; /home/container-user/.claude-json/claude.json &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /home/container-user/.claude-json/claude.json /home/container-user/.claude.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;devcontainer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;claude-json-playwright-setup:/home/container-user/.claude-json&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;claude-json-playwright-setup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;setup-playwright-playground-debian_claude-json-playwright-setup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;post-container-install.sh&lt;/code&gt;&lt;/strong&gt; — fix ownership on the new mount, same as the&lt;br&gt;
existing volumes (mounted volumes come up owned by root):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; container-user:container-user /home/container-user/.claude &lt;span class="se"&gt;\&lt;/span&gt;
                                            /home/container-user/.claude-json &lt;span class="se"&gt;\&lt;/span&gt;
                                            /home/container-user/.copilot &lt;span class="se"&gt;\&lt;/span&gt;
                                            ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.claude/settings.json&lt;/code&gt; and &lt;code&gt;.claude/settings.local.json&lt;/code&gt; also gained a few&lt;br&gt;
permission entries (&lt;code&gt;docker volume *&lt;/code&gt;, &lt;code&gt;docker run *&lt;/code&gt;, &lt;code&gt;docker compose *&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;docker history *&lt;/code&gt;, and a &lt;code&gt;find ... -iname *.copilot*&lt;/code&gt;) needed to diagnose&lt;br&gt;
and rebuild the container while working through this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveat: atomic writes
&lt;/h2&gt;

&lt;p&gt;If an app ever persists the file via write-temp-then-rename (an atomic&lt;br&gt;
write) instead of writing in place, the rename replaces the symlink with a&lt;br&gt;
plain file inside the container's ephemeral layer. No crash — but&lt;br&gt;
persistence silently stops working until the next image build recreates the&lt;br&gt;
symlink. Worth checking if login state ever mysteriously stops surviving a&lt;br&gt;
rebuild again.&lt;/p&gt;

</description>
      <category>claudecli</category>
      <category>devcontainer</category>
    </item>
    <item>
      <title>Bring nvm as a Command to Your CLI: Demystifying the Shell Function</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Wed, 12 Aug 2026 13:35:05 +0000</pubDate>
      <link>https://dev.to/sukkergris/bring-nvm-as-a-command-to-your-cli-demystifying-the-shell-function-2kg4</link>
      <guid>https://dev.to/sukkergris/bring-nvm-as-a-command-to-your-cli-demystifying-the-shell-function-2kg4</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every frontend and full-stack developer has typed &lt;code&gt;nvm install&lt;/code&gt; or &lt;code&gt;nvm use&lt;/code&gt; into their terminal. It feels like any other CLI tool—just like git, docker, or npm.&lt;/p&gt;

&lt;p&gt;However, if you try to call nvm inside a Docker build, devcontainer, a CI/CD pipeline script, or a non-interactive shell, you are immediately met with the dreaded error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvm: &lt;span class="nb"&gt;command &lt;/span&gt;not found.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Understanding why this happens—and how to properly expose nvm as a first-class command in your CLI workflows—requires looking past the illusion of the binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Myth of the Binary
&lt;/h2&gt;

&lt;p&gt;Traditional CLI tools are standalone executable binaries located somewhere in your system's &lt;code&gt;$PATH&lt;/code&gt; (e.g., &lt;code&gt;/usr/local/bin/git&lt;/code&gt;). When you run them, the operating system spawns an isolated subshell process to execute the command, return the output, and exit.&lt;/p&gt;

&lt;p&gt;nvm (Node Version Manager) cannot work this way.&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;nvm&lt;/code&gt;’s entire job is to dynamically rewrite your parent shell’s environment variables—specifically shifting paths in &lt;code&gt;$PATH&lt;/code&gt; to point to different Node.js runtime versions—a traditional &lt;em&gt;binary running in an isolated subshell&lt;/em&gt; &lt;strong&gt;cannot&lt;/strong&gt; &lt;em&gt;modify the environment of the shell you are actively typing into&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Instead, nvm is a &lt;strong&gt;shell script function&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. How &lt;code&gt;nvm&lt;/code&gt; Actually Registers as a Command
&lt;/h2&gt;

&lt;p&gt;To make nvm behave like a command, it must be loaded directly into the memory of your active shell session. This is achieved through sourcing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NVM_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.nvm"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When your terminal starts up, this block reads the nvm.sh script directly into your shell process. This registers nvm as an active shell function, making it instantly available as a command.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Fixing Missing nvm Across Environments
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;nvm&lt;/code&gt; relies on shell initialization files, it often breaks when moving across different environments like local machines, dotfile setups, and devcontainers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario A: Clean Dotfiles &amp;amp; Modular Scripts
&lt;/h3&gt;

&lt;p&gt;If you manage your dotfiles using package managers like GNU Stow, avoid cluttering your core .bashrc or .zshrc. Instead, isolate your tool initializations into a dedicated script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.scripts/external-tools.sh&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NVM_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.nvm"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="se"&gt;\.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/bash_completion"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/bash_completion"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, cleanly source it from your shell profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.scripts/external-tools.sh"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.scripts/external-tools.sh"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scenario B: Docker &amp;amp; Devcontainers
&lt;/h3&gt;

&lt;p&gt;Container environments often start clean or override user profiles, dropping the NVM initialization block. To guarantee nvm is always present for container users, bake the loader system-wide via /etc/bash.bashrc in your Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export NVM_DIR="/home/${USERNAME}/.nvm"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/bash.bashrc &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[ -s "$NVM_DIR/nvm.sh" ] &amp;amp;&amp;amp; \. "$NVM_DIR/nvm.sh"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/bash.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;nvm masquerades as a standard command, but its power stems from how it integrates directly with your shell's runtime environment. By treating nvm for what it is - a shell function requiring explicit initialization - you can eliminate "command not found" errors across your local machine, dotfile repositories, and containerized development workflows.&lt;/p&gt;

</description>
      <category>devcontainer</category>
      <category>shell</category>
      <category>nvm</category>
    </item>
    <item>
      <title>Keep logged in to Claude between container builds</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Tue, 11 Aug 2026 07:08:14 +0000</pubDate>
      <link>https://dev.to/sukkergris/keep-logged-in-to-claude-between-container-builds-430h</link>
      <guid>https://dev.to/sukkergris/keep-logged-in-to-claude-between-container-builds-430h</guid>
      <description>&lt;p&gt;This post is targeted devcontainer users not running the containers as root!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"remoteUser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the end, this is basically a post about mounting folders and ensuring the right folder permissions - when not running with root permissions or as the root user.&lt;/p&gt;

&lt;p&gt;You probably have a setup looking something like this:&lt;br&gt;
&lt;code&gt;devcontainer.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Customer-Name - Dev machine - Debian"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dockerComposeFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docker-compose.yml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"workspaceFolder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/xyz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"remoteUser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"container-user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;#This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;way!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mounts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  
     &lt;/span&gt;&lt;span class="s2"&gt;"source=claude-${localWorkspaceFolderBasename},target=/home/container-user/.claude,type=volume"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="s2"&gt;"source=continue-${localWorkspaceFolderBasename},target=/home/container-user/.continue,type=volume"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"postCreateCommand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bash ${containerWorkspaceFolder}/.devcontainer/debian/post-container-install.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Problem: Root-Owned Named Volumes
&lt;/h2&gt;

&lt;p&gt;When you run a container as a non-root user (like container-user), you immediately run into a distinct permission trap—especially on macOS hosts.&lt;/p&gt;

&lt;p&gt;When Docker creates named volumes (like the ones for .claude and .continue), it defaults to creating them as the root user. If your container-user tries to write configuration files or save chat history to these directories, they will be hit with a Permission denied error.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: The Post-Install Script
&lt;/h2&gt;

&lt;p&gt;To fix this, we use the postCreateCommand to trigger a script that corrects the permissions from inside the container after the volumes are mounted.&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;post-container-install.sh&lt;/code&gt; script should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;#!/usr/bin/env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bash&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Fix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;permissions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;mounted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;volumes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;since&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;they&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;owned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;we&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;want&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;them&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;owned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;user.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;chown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-R&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container-user:container-user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/home/container-user/.claude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
                                             &lt;/span&gt;&lt;span class="err"&gt;/home/container-user/.continue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;/dev/&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Devcontainer setup complete!"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this works
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;sudo chown&lt;/code&gt; command explicitly transfers ownership of the mounted volumes from root back to your non-root user. Because these are Docker named volumes rather than direct host bind mounts, the permission change persists across container rebuilds. This keeps your AI assistant extensions functioning correctly, ensuring your configuration and history remain intact and writable.&lt;/p&gt;

&lt;p&gt;No more logging into Claude between builds!&lt;/p&gt;

&lt;p&gt;Enjoy :)&lt;/p&gt;

</description>
      <category>devcontainer</category>
      <category>docker</category>
      <category>claude</category>
    </item>
    <item>
      <title>Share .ssh/config with your devcontainer</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Thu, 25 Jun 2026 07:34:42 +0000</pubDate>
      <link>https://dev.to/sukkergris/share-sshconfig-with-your-devcontainer-22i9</link>
      <guid>https://dev.to/sukkergris/share-sshconfig-with-your-devcontainer-22i9</guid>
      <description>&lt;h2&gt;
  
  
  How to share your ~/.ssh/config with devcontainers
&lt;/h2&gt;

&lt;p&gt;The purpose is to enable using git with your provider directly from the devcontainer.&lt;/p&gt;

&lt;p&gt;The problem is when running on mac-OS you typically add &lt;code&gt;UseKeychain yes&lt;/code&gt; to your config file. Which is a mac specific setting killing your ssh agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MacOS - first&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;~/.ssh&lt;/code&gt; folder probably looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ tree
&lt;span class="nb"&gt;.&lt;/span&gt;
├── config
├── hetzner_id_ed25519
├── hetzner_id_ed25519.pub
├── known_hosts
├── known_hosts.old
├── README.md
├── sukkerfrit.github
├── sukkerfrit.github.pub
└── test.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the content of your &lt;code&gt;config&lt;/code&gt; something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Host tahh
    HostName github.com
    User git
    AddKeysToAgent &lt;span class="nb"&gt;yes
    &lt;/span&gt;IdentityFile ~/.ssh/sukkerfrit.github
    IdentitiesOnly &lt;span class="nb"&gt;yes
    &lt;/span&gt;ForwardAgent &lt;span class="nb"&gt;yes

&lt;/span&gt;Host hetzner-ktk-test
    HostName xx.xx.xx.xx
    User root
    AddKeysToAgent &lt;span class="nb"&gt;yes
    &lt;/span&gt;UseKeychain &lt;span class="nb"&gt;yes
    &lt;/span&gt;IdentityFile ~/.ssh/hetzner_id_ed25519
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;UseKeychain&lt;/strong&gt;&lt;br&gt;
Is a mac-OS specific SSH setting. We use it to save and fetch the SSH-key passphrase automatically.&lt;/p&gt;

&lt;p&gt;BUT it doesn't work on Linux and will actually course an exception on load rendering using eg. git directly from your devcontainer impossible.&lt;/p&gt;

&lt;p&gt;This image is just working on &lt;code&gt;root&lt;/code&gt; which i normally never do. I use to create a &lt;code&gt;container-user&lt;/code&gt; in my container - I guess I was just lazy today.&lt;/p&gt;

&lt;p&gt;This is way I share my &lt;code&gt;~/.ssh/config&lt;/code&gt; with devcontainers.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Add host's ~/.ssh to docker as mount
&lt;/h2&gt;

&lt;p&gt;My 'trick' is to just mount my host's .ssh folder as read only - but to eg. &lt;strong&gt;/root/.sshtemplate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then you don't end up editing your host's config.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;devcontainer.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "some-funkey-name",
  "dockerComposeFile": "docker-compose.yml",
  "service": "development",
  "workspaceFolder": "/xyz",
  "postCreateCommand": "./.devcontainer/post-container-install.sh",
  "mounts": [
    "source=${localEnv:HOME}/.ssh,target=/root/.sshtemplate,type=bind,readonly,consistency=cached"
  ],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the target if you work on a different user.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Copy from .sshtemplate -&amp;gt; .ssh
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;copy-ssh-files.sh&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; /root/.sshtemplate &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /root/.sshtemplate/. ~/.ssh/
  &lt;span class="nb"&gt;chmod &lt;/span&gt;700 ~/.ssh
  &lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.ssh/&lt;span class="k"&gt;*&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Remove lines with &lt;code&gt;UseKeychain&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;remove-usekeychain-lines.sh&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;

&lt;span class="c"&gt;# Remove UseKeychain (case-insensitive) from a config file if it exists&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'/UseKeychain/I d'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"UseKeychain removed from &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Create post install file
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;post-container-install.sh&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
...
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SCRIPTS_DIR&lt;/span&gt;&lt;span class="s2"&gt;/copy-ssh-files.sh"&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SCRIPTS_DIR&lt;/span&gt;&lt;span class="s2"&gt;/remove-usekeychain-lines.sh"&lt;/span&gt; ~/.ssh/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enjoy!&lt;/p&gt;

</description>
      <category>macos</category>
      <category>vscode</category>
      <category>devcontainer</category>
      <category>ssh</category>
    </item>
    <item>
      <title>AI - We need some time apart</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Mon, 22 Jun 2026 08:20:06 +0000</pubDate>
      <link>https://dev.to/sukkergris/ai-we-need-som-time-apart-hnm</link>
      <guid>https://dev.to/sukkergris/ai-we-need-som-time-apart-hnm</guid>
      <description>&lt;p&gt;&lt;strong&gt;Dear AI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I think we overused each other the past four months. We really just went head over heels in this relationship. It's been a wild experience and I'm grateful for that. But now I find myself deprived of joy and constantly tired down and even though I know you have had the best of intentions - I think we need to spend some time apart.&lt;/p&gt;

&lt;p&gt;It's not you - it's me. I have just come to despise you for everything you are and stands for. Unfortunately you have infiltrated the very fabric of the world I used to love so avoiding you is like avoiding breathing.&lt;/p&gt;

&lt;p&gt;Could we just have a more professional relationship for now? I'll acknowledge your existence and work with you when need be.&lt;/p&gt;

&lt;p&gt;I'll even avoid arguing with your believers. I'll stop warning about outsourcing the ability to think and nut polite whenever people prophecies about an AI utopia where code writes itself and cares can drive without a chauffeur. I'll restrain myself from reminding people that a session with you burns more co2 into the atmosphere than leveling the toilet light on - for your entire life!&lt;/p&gt;

&lt;p&gt;How you steal our work, drink our rivers, take our land, burn our resources, leave us joyless, jobless while slowly gaslighting everyone into believing - tomorrow you’ll be better, you can change, you have our best interests on your mind, you do it all for us.&lt;/p&gt;

&lt;p&gt;Best regards and wishes&lt;/p&gt;

&lt;p&gt;Yours forever no more!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cognitivedebt</category>
    </item>
    <item>
      <title>Confessions of an AI Sceptic</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Sun, 08 Mar 2026 10:52:16 +0000</pubDate>
      <link>https://dev.to/sukkergris/confessions-of-an-ai-sceptic-4j7e</link>
      <guid>https://dev.to/sukkergris/confessions-of-an-ai-sceptic-4j7e</guid>
      <description>&lt;p&gt;First of all — yes, I’m a real sceptic. By heart.&lt;br&gt;
To make my confession public: I don’t believe we’ll see &lt;strong&gt;unsupervised&lt;/strong&gt; self-driving cars anytime soon either.&lt;br&gt;
Sounds heretical? Then I’ll call you out as a true AI believer and kindly ask you to move along. Trust me — this post isn’t for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI sceptic =/= AI hater
&lt;/h2&gt;

&lt;p&gt;I use AI all the time, and most of the time I enjoy it.&lt;/p&gt;

&lt;p&gt;It helps me in several practical ways. Recently it helped me find a cheap part for my old bike — loved it. And as a daily assistant it’s often genuinely useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. As a Cognitive Partner
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Peer review (Not all my ideas are bullet proof)&lt;/li&gt;
&lt;li&gt;Bounce ideas&lt;/li&gt;
&lt;li&gt;Brainstorming&lt;/li&gt;
&lt;li&gt;Exploring alternative approaches&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. To Knowledge Retrieve and Explore
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;"Google" syntax I just can't remember&lt;/li&gt;
&lt;li&gt;Scaffold simple routines&lt;/li&gt;
&lt;li&gt;Research topics&lt;/li&gt;
&lt;li&gt;Comparing options&lt;/li&gt;
&lt;li&gt;Finding parts/products&lt;/li&gt;
&lt;li&gt;Explaining unfamiliar concepts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So yes — I both use and like AI. Remember that.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Company Took the Blue AI Pill
&lt;/h2&gt;

&lt;p&gt;And down the rabbit hole I went! &lt;/p&gt;

&lt;h3&gt;
  
  
  Premise
&lt;/h3&gt;

&lt;p&gt;Like the rest of the industry, we cannot ignore — or at least must investigate — Big Tech’s AI promises. This week my team asked me to take a deep dive into how AI could help make us more competitive.&lt;/p&gt;

&lt;p&gt;The premise for this investigation is to explore how AI can help us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deliver faster&lt;/li&gt;
&lt;li&gt;Deliver higher quality (At least)&lt;/li&gt;
&lt;li&gt;Deliver sustainable solutions (longevity)&lt;/li&gt;
&lt;li&gt;Deliver more value to the customer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if possible, test whether we can establish a workflow where we deliver solely through &lt;em&gt;spec-driven prompting&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Sounds like fun - right?!??&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the Documentation
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Day 1:&lt;/em&gt;&lt;br&gt;
Sine I myself have never use agentic AI all the way, I spend the first day in pure chaos just getting an overview. THAT actually was fun!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Day 2:&lt;/em&gt;&lt;br&gt;
I felt like I need something more tangible to work on. So we decided to, agentically, to setup &lt;a href="https://code.visualstudio.com/docs/copilot/overview" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt; in VS Code and create a Todo-app using our favorite tech-stack. So I just read the documentation, and bit for bit I all came together.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Day 3:&lt;/em&gt;&lt;br&gt;
Meetings&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Day 4:&lt;/em&gt;&lt;br&gt;
I’m starting to realize that AI needs a lot of guardrails and instructions. There even exists a standard for how to describe various '&lt;a href="https://agentskills.io/home" rel="noopener noreferrer"&gt;skill's&lt;/a&gt;' the AI needs to solve specific tasks.&lt;/p&gt;

&lt;p&gt;This realization raised a concern:&lt;/p&gt;

&lt;p&gt;Are we heading toward endless meetings that sound like&lt;br&gt;
“You didn’t specify the XYZ prompt/skill correctly.”&lt;/p&gt;

&lt;p&gt;If coding isn’t my job anymore, then prompting must be the new craft to master.&lt;/p&gt;

&lt;p&gt;And if defining skills, agents, memories, and instructions becomes the core work, then companies like ours should probably start systematically managing those definitions — likely as structured .md files.&lt;/p&gt;

&lt;p&gt;Another thing became clear: the more examples you provide, the more precise the AI becomes.&lt;/p&gt;

&lt;p&gt;That part I actually like. It suggests we could achieve more consistent solutions.&lt;/p&gt;

&lt;p&gt;Still, I remain cautious. These systems are fundamentally non-deterministic, and I’m not convinced they’ll reliably stick to specifications.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Day 5:&lt;/em&gt;&lt;br&gt;
Mostly meetings. But I did get to chose the exact tech stack.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET 10, C#, Blazor, Docker, Devcontainer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Saturday:&lt;/em&gt;&lt;br&gt;
I felt like coding.&lt;/p&gt;

&lt;p&gt;If ultra-productive AI agents really lead to faster delivery, then any workflow this agile absolutely needs a CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;For years I’ve used Nuke Build as my preferred pipeline orchestrator. However, its current trajectory seems uncertain, so I’m considering FAKE as a possible replacement.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Wall
&lt;/h3&gt;

&lt;p&gt;Then I started prompting.&lt;/p&gt;

&lt;p&gt;And let me say this clearly: I’m not impressed.&lt;/p&gt;

&lt;p&gt;If I weren’t already a seasoned developer, I might easily have trusted the output. That would have been a mistake.&lt;/p&gt;

&lt;p&gt;The AI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Corrupted my Devcontainer Dockerfile&lt;/li&gt;
&lt;li&gt;Tried installing strange packages&lt;/li&gt;
&lt;li&gt;Added unnecessary tools&lt;/li&gt;
&lt;li&gt;Overcomplicated the build configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: it made a mess.&lt;/p&gt;

&lt;p&gt;At this stage, reading the documentation and incrementally building the project myself seems significantly faster.&lt;/p&gt;

&lt;p&gt;One might argue that I should have stuck to a more widely known and used orchestrator. But my counterargument would be simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If AI can only operate effectively inside the most common and well-documented stacks — and only with heavy guidance, guardrails, and extensive examples — then its practical value is far more limited than its evangelists suggest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Next week
&lt;/h2&gt;

&lt;p&gt;Next week I’ll continue the experiment. Once the project structure is properly established, I’ll test whether AI can beat me at adding features.&lt;/p&gt;

&lt;p&gt;Until then:&lt;/p&gt;

&lt;p&gt;I remain sceptical.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agentic</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Install multiple versions of .NET in a Docker image</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Sat, 07 Mar 2026 17:17:48 +0000</pubDate>
      <link>https://dev.to/sukkergris/install-multipel-versions-of-net-in-a-docker-image-53mf</link>
      <guid>https://dev.to/sukkergris/install-multipel-versions-of-net-in-a-docker-image-53mf</guid>
      <description>&lt;p&gt;Since I’ve become increasingly unsure about the current state of &lt;a href="https://nuke.build/" rel="noopener noreferrer"&gt;NUKE Build&lt;/a&gt;, I’m experimenting with &lt;a href="https://fake.build/" rel="noopener noreferrer"&gt;FAKE&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;FAKE depends on .NET 6, but I’ve already moved to .NET 10. So here’s a container image that can run FAKE while still keeping a newer .NET SDK installed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;:&lt;br&gt;
The container runs using a non-root user. This mirrors how CI environments and devcontainers typically operate and avoids installing tools as root. For convenience during experimentation the user is granted passwordless sudo (this should never be used in production).&lt;/p&gt;

&lt;p&gt;Here is the Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; debian:12-slim&lt;/span&gt;

&lt;span class="c"&gt;# Use bash for the shell (default is sh)&lt;/span&gt;
&lt;span class="k"&gt;SHELL&lt;/span&gt;&lt;span class="s"&gt; ["/bin/bash", "-o", "pipefail", "-c"]&lt;/span&gt;

&lt;span class="c"&gt;# Set environment variables&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; USERNAME=container-user \&lt;/span&gt;
    HOME_DIR="/home/container-user" \
    DOTNET_ROOT=/opt/dotnet \
    DEBIAN_FRONTEND=noninteractive

&lt;span class="c"&gt;# Update PATH once with all directories&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/usr/local/bin:/opt/dotnet:/opt/dotnet/tools:/home/container-user/.local/bin:/home/container-user/.dotnet/tools:$PATH"&lt;/span&gt;

&lt;span class="c"&gt;# Install base dependencies used later in the image build.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;        curl &lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Create non-root user AFTER sudo is installed&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; USER_UID=1000&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; USER_GID=1000&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;groupadd &lt;span class="nt"&gt;--gid&lt;/span&gt; &lt;span class="nv"&gt;$USER_GID&lt;/span&gt; &lt;span class="nv"&gt;$USERNAME&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    useradd &lt;span class="nt"&gt;--uid&lt;/span&gt; &lt;span class="nv"&gt;$USER_UID&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; &lt;span class="nv"&gt;$USER_GID&lt;/span&gt; &lt;span class="nt"&gt;--create-home&lt;/span&gt; &lt;span class="nt"&gt;--shell&lt;/span&gt; /bin/bash &lt;span class="nv"&gt;$USERNAME&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USERNAME&lt;/span&gt;&lt;span class="s2"&gt; ALL=(ALL) NOPASSWD:ALL"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/sudoers

&lt;span class="c"&gt;# Install .NET SDK (consolidated into one layer, fixed version)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://dot.net/v1/dotnet-install.sh &lt;span class="nt"&gt;-o&lt;/span&gt; dotnet-install.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chmod&lt;/span&gt; +x dotnet-install.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    ./dotnet-install.sh &lt;span class="nt"&gt;--version&lt;/span&gt; 10.0.100 &lt;span class="nt"&gt;--install-dir&lt;/span&gt; &lt;span class="nv"&gt;$DOTNET_ROOT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    ./dotnet-install.sh &lt;span class="nt"&gt;--version&lt;/span&gt;  6.0.428 &lt;span class="nt"&gt;--install-dir&lt;/span&gt; &lt;span class="nv"&gt;$DOTNET_ROOT&lt;/span&gt; &lt;span class="nt"&gt;--skip-non-versioned-files&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm &lt;/span&gt;dotnet-install.sh

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; $USERNAME&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; $HOME_DIR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build: &lt;code&gt;docker build -f build/Dockerfile -t fake-runner:dev .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run as non-root: &lt;code&gt;docker run --rm -it fake-runner:dev /bin/bash&lt;/code&gt;&lt;br&gt;
Run as root: &lt;code&gt;docker run --rm -it --user root fake-runner:dev /bin/bash&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft6zt5sxpus76d29fhdis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft6zt5sxpus76d29fhdis.png" alt=" " width="788" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thats it :)&lt;/p&gt;

</description>
      <category>devcontainer</category>
      <category>docker</category>
      <category>fake</category>
      <category>fsharp</category>
    </item>
  </channel>
</rss>
