<?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.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>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>
    <item>
      <title>AI, Cognitive Debt, and Ownership</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Wed, 04 Mar 2026 07:43:56 +0000</pubDate>
      <link>https://dev.to/sukkergris/ai-cognitive-debt-and-ownership-3jcm</link>
      <guid>https://dev.to/sukkergris/ai-cognitive-debt-and-ownership-3jcm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post is inspired by the reflections shared in this talk:&lt;br&gt;
&lt;a href="https://youtu.be/Tk0hIOAwf6M?si=BrPjhPG1-CJqf0sZ" rel="noopener noreferrer"&gt;The Dark Side of AI Code Generation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lately at work we are, like the rest of the world, embracing AI and incorporating it into our daily lives and workflows.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;But uncritical usage of AI has a dark side!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The dark side
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When Using AI, You Risk &lt;em&gt;Building Knowledge Gaps&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI-Generated Code Can Train You to Accept &lt;em&gt;Complexity You Don’t Understand&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI increases output. That is not the same as increasing understanding.&lt;/p&gt;

&lt;p&gt;If we repeatedly merge code we did not fully reason about, we accumulate something more dangerous than technical debt:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We accumulate cognitive debt.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Cognitive Debt?
&lt;/h2&gt;

&lt;p&gt;Cognitive Debt is the interest you pay in time and stress when you have to re-learn your own system through code you never truly understood.&lt;/p&gt;

&lt;p&gt;It compounds.&lt;/p&gt;

&lt;p&gt;You pay the interest when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debugging production incidents&lt;/li&gt;
&lt;li&gt;Refactoring fragile areas&lt;/li&gt;
&lt;li&gt;Onboarding new developers&lt;/li&gt;
&lt;li&gt;Explaining design decisions you cannot fully justify&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;AI is not the problem.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Unowned complexity is.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Shared Theory: The Real Asset
&lt;/h2&gt;

&lt;p&gt;Every healthy engineering team shares a theory of the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How it works&lt;/li&gt;
&lt;li&gt;Why decisions were made&lt;/li&gt;
&lt;li&gt;Where the boundaries are&lt;/li&gt;
&lt;li&gt;What must never break&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This shared understanding is what allows teams to move fast safely.&lt;/p&gt;

&lt;p&gt;If AI accelerates code production without strengthening shared theory, you are trading short-term speed for long-term fragility.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI-PR Review Framework
&lt;/h2&gt;

&lt;p&gt;AI should be used to &lt;strong&gt;increase shared understanding&lt;/strong&gt; — not reduce it.&lt;/p&gt;

&lt;p&gt;If you do not understand the code, you do not own it.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Review Like a Junior Developer
&lt;/h3&gt;

&lt;p&gt;Read the PR as if you are new to the codebase.&lt;/p&gt;

&lt;p&gt;Ask yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can I explain every single line to a colleague?&lt;/li&gt;
&lt;li&gt;Do I understand why this approach was chosen?&lt;/li&gt;
&lt;li&gt;Could I rewrite this without using AI?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer to any of these is no, you are not done reviewing.&lt;/p&gt;

&lt;p&gt;Read deeper.&lt;br&gt;&lt;br&gt;
Refactor it.&lt;br&gt;&lt;br&gt;
Simplify it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The standard is this:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I feel confident debugging this in production at 3 AM.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If that sentence feels false, the code is not ready.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. The PR Owner Must Document Decisions
&lt;/h3&gt;

&lt;p&gt;The person who prompted or introduced AI-generated code must update a decision record (Confluence, ADR, etc.) with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the code solves&lt;/li&gt;
&lt;li&gt;Why this design was chosen&lt;/li&gt;
&lt;li&gt;Known edge cases&lt;/li&gt;
&lt;li&gt;Trade-offs considered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This forces reasoning before merging.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Rotate Ownership of Critical Areas
&lt;/h3&gt;

&lt;p&gt;Make sure everyone on the team can describe the system’s most critical components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a list of critical areas&lt;/li&gt;
&lt;li&gt;Rotate code walkthrough responsibility&lt;/li&gt;
&lt;li&gt;Require reviewers to update the decision record during review&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Examples of critical areas:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Payments&lt;/li&gt;
&lt;li&gt;Application pipeline (Program.cs, Startup.cs, middleware flow)&lt;/li&gt;
&lt;li&gt;Infrastructure boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If only one person understands a critical system, you are already in debt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Guardrails for AI Usage
&lt;/h2&gt;

&lt;p&gt;Practical constraints reduce risk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep AI-assisted PRs small&lt;/li&gt;
&lt;li&gt;Require tests before merge&lt;/li&gt;
&lt;li&gt;Prefer AI for scaffolding and refactoring, not architectural decisions&lt;/li&gt;
&lt;li&gt;Enforce clear architectural boundaries&lt;/li&gt;
&lt;li&gt;Reject complexity you cannot explain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI should increase clarity.&lt;br&gt;&lt;br&gt;
If it increases opacity, something is wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Debugging Confidence Standard
&lt;/h2&gt;

&lt;p&gt;Speed without comprehension is fragile.&lt;/p&gt;

&lt;p&gt;Before merging AI-generated code, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Would I confidently debug this without asking AI to explain it back to me?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is no, the cost has only been postponed.&lt;/p&gt;

&lt;p&gt;And postponed cost compounds.&lt;/p&gt;




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

&lt;p&gt;AI is a powerful amplifier.&lt;/p&gt;

&lt;p&gt;It can amplify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understanding&lt;/li&gt;
&lt;li&gt;Discipline&lt;/li&gt;
&lt;li&gt;Shared ownership&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or it can amplify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complexity&lt;/li&gt;
&lt;li&gt;Fragility&lt;/li&gt;
&lt;li&gt;Dependency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference is not in the tool.&lt;/p&gt;

&lt;p&gt;It is in whether we refuse to merge what we do not understand.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cognitivedebt</category>
    </item>
    <item>
      <title>How to Disable Github Copilot Autocomplete in vs code</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Thu, 05 Feb 2026 12:39:48 +0000</pubDate>
      <link>https://dev.to/sukkergris/how-to-disable-github-copilot-autocomplete-in-vs-code-1a5o</link>
      <guid>https://dev.to/sukkergris/how-to-disable-github-copilot-autocomplete-in-vs-code-1a5o</guid>
      <description>&lt;h1&gt;
  
  
  Why?
&lt;/h1&gt;

&lt;p&gt;I actually like AI. I use it—like, all the time. But being a seasoned programmer, I have grown increasingly frustrated with the invasive suggestions popping up all over the second I start typing in my favorite IDE.&lt;/p&gt;

&lt;p&gt;This is not a rant—BUT. Why, why, why isn't there just a button to turn off this noise?&lt;/p&gt;

&lt;p&gt;Add this to your &lt;code&gt;settings.json&lt;/code&gt; to silence Copilot's "help":&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;"github.copilot.enable"&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="nl"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"plaintext"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"markdown"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>vscode</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>(4)Creating the Pinnacle of Niche Software: Abandoning localhost:1234 - locally!</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Mon, 29 Dec 2025 16:34:33 +0000</pubDate>
      <link>https://dev.to/sukkergris/4creating-the-pinnacle-of-niche-software-abandoning-localhost1234-locally-g2g</link>
      <guid>https://dev.to/sukkergris/4creating-the-pinnacle-of-niche-software-abandoning-localhost1234-locally-g2g</guid>
      <description>&lt;h2&gt;
  
  
  About
&lt;/h2&gt;

&lt;p&gt;The concrete implementation was made on MacOS, but it should be quite easy to alter and adopt this example to both Windows and Linux.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusively tested with Vite 6+!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this article we will implement the ideas presented here - &lt;a href="https://dev.to/sukkergris/production-parity-routing-local-traffic-with-a-reverse-proxy-2ndh"&gt;Production Parity: Routing Local Traffic with a Reverse Proxy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main idea is to achieve parity between our development setup and production.&lt;/p&gt;

&lt;p&gt;You might think: 'Why would this be necessary?' - The argumentation for achieving environment parity is covered here: &lt;a href="https://dev.to/sukkergris/the-red-pill-of-software-delivery-unmasking-magic-code-and-building-for-reality-1ng9"&gt;the-red-pill-of-software-delivery-unmasking-magic-code-and-building-for-reality&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article is a continuation of &lt;a href="https://dev.to/sukkergris/3creating-the-pinnacle-of-niche-software-using-vite-plugin-elm-watch-36m8"&gt;creating-the-pinnacle-of-niche-software-using-vite-plugin-elm-watch&lt;/a&gt; and the example created there will be extended in the branch named &lt;code&gt;feature/use-custom-domain&lt;/code&gt; - &lt;a href="https://github.com/sukkergris/elm-first-vite-example/tree/feature/use-custom-domain" rel="noopener noreferrer"&gt;Link!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Goals
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Use a custom domain name and completely ditch the usage of &lt;code&gt;localhost:port&lt;/code&gt; when working on our solution from the browser&lt;/li&gt;
&lt;li&gt;Develop from a devcontainer&lt;/li&gt;
&lt;li&gt;Enable HTTPS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let's go through how we achieve this fine objective :)&lt;/p&gt;

&lt;h2&gt;
  
  
  The moving parts
&lt;/h2&gt;

&lt;p&gt;Next up we will setup the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add nginx to the solution&lt;/li&gt;
&lt;li&gt;Configure our &lt;code&gt;hosts&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Configure nginx&lt;/li&gt;
&lt;li&gt;Create and trust certificates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reverse Proxy
&lt;/h3&gt;

&lt;p&gt;First of all we need to add a reverse proxy like &lt;a href="https://nginx.org/" rel="noopener noreferrer"&gt;nginx&lt;/a&gt; to handle routing from a url/domain to the vite server. This won't break HRL and will bring all the benefits discussed earlier. I'll use nginx throughout this article.&lt;/p&gt;

&lt;p&gt;If you are following along from the previous article, it should be quite straight forward to extend the the code in the following. A working example can be found here: &lt;a href="https://github.com/sukkergris/elm-first-vite-example/tree/feature/use-custom-domain" rel="noopener noreferrer"&gt;Link!&lt;/a&gt; in the branch &lt;code&gt;use-custom-domanin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add nginx&lt;/strong&gt;&lt;br&gt;
First we'll add nginx to the &lt;code&gt;docker-compose.yml&lt;/code&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elm-first&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;..&lt;/span&gt;

  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elm-first-nginx&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;
    &lt;span class="c1"&gt;# volumes:&lt;/span&gt;
      &lt;span class="c1"&gt;# - ./nginx.conf:/etc/nginx/nginx.conf:ro&lt;/span&gt;
      &lt;span class="c1"&gt;# - ./ssl:/etc/nginx/certs:ro&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dev&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;elm-first-vite-example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Add endpoint to your hosts file&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On mac it's simply done by:&lt;br&gt;
&lt;code&gt;&amp;gt; sudo vi /etc/hosts&lt;/code&gt; and add&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1       happy.now www.happy.now
127.0.0.1       happy.dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file an restart the devcontainer.&lt;br&gt;
I had some troubles with when the mappings in the &lt;code&gt;hosts&lt;/code&gt; file were picked up by the browsers.&lt;/p&gt;

&lt;p&gt;But now we can access the default nginx site by simply using: &lt;a href="http://happy.now" rel="noopener noreferrer"&gt;happy.now&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Observe that using &lt;a href="http://happy.dev" rel="noopener noreferrer"&gt;happy.dev&lt;/a&gt; will redirect to &lt;code&gt;https://happy.dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next we'll route to our vite server running on port 3456&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serve the running vite sever&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a custom &lt;em&gt;nginx.conf&lt;/em&gt; &lt;code&gt;touch .d
evcontainer/nginx.conf&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;worker_connections&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;# Define resolver to handle host.docker.internal&lt;/span&gt;
    &lt;span class="kn"&gt;resolver&lt;/span&gt; &lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="s"&gt;.0.11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;www.happy.dev&lt;/span&gt; &lt;span class="s"&gt;happy.dev&lt;/span&gt; &lt;span class="s"&gt;www.happy.now&lt;/span&gt; &lt;span class="s"&gt;happy.now&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;# Proxy frontend requests to Elm dev server&lt;/span&gt;
        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$frontend_upstream&lt;/span&gt; &lt;span class="nf"&gt;host.docker.internal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3456&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://&lt;/span&gt;&lt;span class="nv"&gt;$frontend_upstream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;'upgrade'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_cache_bypass&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;sub_filter&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;/head&amp;gt;'&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;meta&lt;/span&gt; &lt;span class="s"&gt;name="environment-name"&lt;/span&gt; &lt;span class="s"&gt;content="local"&lt;/span&gt; &lt;span class="n"&gt;/&amp;gt;&amp;lt;/head&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;sub_filter_once&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="c1"&gt;# Only apply sub_filter to index.html&lt;/span&gt;
            &lt;span class="c1"&gt;# sub_filter_types text/html;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Uncomment in the &lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;./nginx.conf:/etc/nginx/nginx.conf:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;From the &lt;strong&gt;host machine!&lt;/strong&gt; run: &lt;code&gt;docker exec elm-first-nginx nginx -t&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It the config is ok now run: &lt;code&gt;docker exec elm-first-nginx nginx -s reload&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Start the vite server by running: &lt;code&gt;npm run dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The vite server will bounce access from anything but localhost so update the &lt;code&gt;allowedHosts&lt;/code&gt; whith:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;allowedHosts:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"host.docker.internal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"www.happy.now"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"happy.now"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"www.happy.dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"happy.dev"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Reload the page and be a happy developer now!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally we will enable HTTPS.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enable HTTPS
&lt;/h3&gt;

&lt;p&gt;First we'll need to rearrage the project a bit. Start with stopping the devcontainer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rearrage nginx config&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add the missing nginx folder and add files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project-root/
├── .devcontainer/
│   ├── ssl/
│   ├── devcontainer.json
│   ├── docker-compose.yml
│   ├── Dockerfile.devmachine
│       └── nginx/
|           ├── nginx.conf
|           └── conf.d/
|               └── proxy_elm_env.inc &amp;lt;-- NEW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new configs should look like this:&lt;br&gt;
&lt;code&gt;nginx.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;worker_connections&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;# Define resolver to handle host.docker.internal&lt;/span&gt;
    &lt;span class="kn"&gt;resolver&lt;/span&gt; &lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="s"&gt;.0.11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;www.happy.dev&lt;/span&gt; &lt;span class="s"&gt;happy.dev&lt;/span&gt; &lt;span class="s"&gt;www.happy.now&lt;/span&gt; &lt;span class="s"&gt;happy.now&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;# Proxy frontend requests to Elm dev server&lt;/span&gt;
        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/conf.d/proxy_elm_env.inc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;www.happy.dev&lt;/span&gt; &lt;span class="s"&gt;happy.dev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt;     &lt;span class="n"&gt;/etc/nginx/certs/happy.dev.crt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/certs/happy.dev.key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/conf.d/proxy_elm_env.inc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;proxy_elm_env.inc&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://&lt;/span&gt;&lt;span class="nv"&gt;$frontend_upstream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;'upgrade'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;proxy_cache_bypass&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;sub_filter&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;/head&amp;gt;'&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;meta&lt;/span&gt; &lt;span class="s"&gt;name="environment-name"&lt;/span&gt; &lt;span class="s"&gt;content="local"&lt;/span&gt; &lt;span class="n"&gt;/&amp;gt;&amp;lt;/head&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Certificates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We need a certificate in order to use HTTPS. Here we'll create a self signed cert.&lt;/p&gt;

&lt;p&gt;Let's use &lt;strong&gt;happy&lt;/strong&gt; as our domain name.&lt;/p&gt;

&lt;p&gt;Since we will be using &lt;a href="https://squidex.io/" rel="noopener noreferrer"&gt;squidex&lt;/a&gt; in the next article we create a cert which can handle both &lt;code&gt;happy.dev&lt;/code&gt; and &lt;code&gt;squidex.happy.dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes on TLD's&lt;/strong&gt;&lt;br&gt;
It's important to notice that some TLD's are special.&lt;br&gt;
&lt;em&gt;dev&lt;/em&gt;, &lt;em&gt;app&lt;/em&gt;, and &lt;em&gt;page&lt;/em&gt; will all have HTTPS enforced by the browser if used. Unlike &lt;em&gt;test&lt;/em&gt;, &lt;em&gt;localhost&lt;/em&gt; or &lt;em&gt;local&lt;/em&gt; which, at least on MacOS, will be treated as safe&lt;/p&gt;
&lt;h3&gt;
  
  
  Method 1 (Not so good)
&lt;/h3&gt;

&lt;p&gt;Using this method will require a cert for each domain name.&lt;br&gt;
Eg. one for both &lt;code&gt;happy.dev&lt;/code&gt; and &lt;code&gt;squidex.happy.dev&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="nb"&gt;cd&lt;/span&gt; /workspace/.devcontainer/ssl
openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-days&lt;/span&gt; 365 &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:2048 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-keyout&lt;/span&gt; happy.dev.key &lt;span class="nt"&gt;-out&lt;/span&gt; happy.dev.crt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/CN=happy.dev"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-addext&lt;/span&gt; &lt;span class="s2"&gt;"subjectAltName=DNS:happy.dev,DNS:*.happy.dev"&lt;/span&gt;

&lt;span class="c"&gt;# Set proper permissions&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 squidex.happy.dev.crt
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 squidex.happy.dev.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat for the happy.dev domain. But I prefer to just use method 3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method 2 (Better)
&lt;/h3&gt;

&lt;p&gt;Using this method will make you only need one certificate.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;req.cnf&lt;/code&gt; file in eg. the &lt;code&gt;.devcontainer/ssh&lt;/code&gt; folder.&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;req]
distinguished_name &lt;span class="o"&gt;=&lt;/span&gt; req_distinguished_name
x509_extensions &lt;span class="o"&gt;=&lt;/span&gt; v3_req
prompt &lt;span class="o"&gt;=&lt;/span&gt; no

&lt;span class="o"&gt;[&lt;/span&gt;req_distinguished_name]
C &lt;span class="o"&gt;=&lt;/span&gt; DK
ST &lt;span class="o"&gt;=&lt;/span&gt; Central Denmark Region
L &lt;span class="o"&gt;=&lt;/span&gt; Tilst
O &lt;span class="o"&gt;=&lt;/span&gt; Kompromisløs
OU &lt;span class="o"&gt;=&lt;/span&gt; IT
CN &lt;span class="o"&gt;=&lt;/span&gt; happy.dev

&lt;span class="o"&gt;[&lt;/span&gt;v3_req]
keyUsage &lt;span class="o"&gt;=&lt;/span&gt; nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage &lt;span class="o"&gt;=&lt;/span&gt; serverAuth
subjectAltName &lt;span class="o"&gt;=&lt;/span&gt; @alt_names

&lt;span class="o"&gt;[&lt;/span&gt;alt_names]
DNS.1 &lt;span class="o"&gt;=&lt;/span&gt; happy.dev
DNS.2 &lt;span class="o"&gt;=&lt;/span&gt; squidex.happy.dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate Private key: &lt;code&gt;openssl genrsa -out happy.dev.key 2048&lt;/code&gt;&lt;br&gt;
Generate Self-Signed Certificate: &lt;code&gt;openssl req -x509 -new -key happy.dev.key -sha256 -days 365 -out happy.dev.crt -config req.cnf -extensions v3_req&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="nb"&gt;chmod &lt;/span&gt;644 happy.dev.crt
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 happy.dev.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trust the Certificate in macOS Keychain
&lt;/h2&gt;

&lt;p&gt;To make browsers on your Mac trust your self-signed certificate, you need to add the certificate to your macOS system keychain and then explicitly set its trust level.&lt;/p&gt;

&lt;p&gt;Most web browsers on macOS, including Chrome, Safari, and Firefox (by default), rely on the certificates stored in the system's Keychain Access utility.&lt;/p&gt;

&lt;p&gt;Here is the step-by-step guide to get your browser to trust the happy.dev certificate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: Open Keychain Access&lt;br&gt;
Open Spotlight Search by pressing Cmd + Space.&lt;/p&gt;

&lt;p&gt;Type Keychain Access and press Enter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: Import the Certificate&lt;br&gt;
In the Keychain Access application, make sure you have the login or System keychain selected on the left sidebar.&lt;/p&gt;

&lt;p&gt;Go to the menu bar and select File &amp;gt; Import Items....&lt;/p&gt;

&lt;p&gt;Navigate to the location of your &lt;code&gt;happy.dev.crt&lt;/code&gt; file, select it, and click Open.&lt;/p&gt;

&lt;p&gt;You may be prompted to enter your administrator password to add the certificate to the keychain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;: Set the Trust Level&lt;br&gt;
In Keychain Access, find your imported certificate. It should be listed under its common name, which is likely happy.dev or a similar identifier.&lt;/p&gt;

&lt;p&gt;Double-click the certificate to open its details window.&lt;/p&gt;

&lt;p&gt;Expand the Trust section.&lt;/p&gt;

&lt;p&gt;You will see a dropdown menu labeled "When using this certificate:". By default, it will say "Use System Defaults."&lt;/p&gt;

&lt;p&gt;Change this dropdown to "Always Trust."&lt;/p&gt;

&lt;p&gt;Close the window. You will be prompted to enter your administrator password again to save the changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: Restart Your Browser&lt;br&gt;
After you've set the trust level, it's a good practice to completely quit and restart your web browser (e.g., Safari, Chrome, or Firefox). This ensures that the browser reloads the certificate trust settings from the system keychain.&lt;/p&gt;

&lt;p&gt;Now, when you navigate to &lt;a href="https://happy.dev" rel="noopener noreferrer"&gt;https://happy.dev&lt;/a&gt;, your browser should recognize the certificate as valid, and you will no longer see the "Your connection is not private" or security warning pages.&lt;/p&gt;
&lt;h2&gt;
  
  
  Update your hostfile
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sudo vi /etc/hosts&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;##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
....
127.0.0.1       happy.dev www.happy.dev        &amp;lt;-- ADD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use a script to add cert
&lt;/h2&gt;

&lt;p&gt;All the above steps can be automated using this 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;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Example&lt;/span&gt;
&lt;span class="c"&gt;# bash&amp;gt; setup-certs.sh happy.dev 365&lt;/span&gt;

&lt;span class="c"&gt;# Default values&lt;/span&gt;
&lt;span class="nv"&gt;CERT_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;localhost&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;DAYS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;365&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;CERT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Generate private key&lt;/span&gt;
openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt;.key"&lt;/span&gt; 2048

&lt;span class="c"&gt;# Generate self-signed certificate&lt;/span&gt;
openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt;.key"&lt;/span&gt; &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-days&lt;/span&gt; &lt;span class="nv"&gt;$DAYS&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-out&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt;.crt"&lt;/span&gt; &lt;span class="nt"&gt;-config&lt;/span&gt; req.cnf &lt;span class="nt"&gt;-extensions&lt;/span&gt; v3_req

&lt;span class="c"&gt;# Set proper permissions&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt;.crt"&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt;.key"&lt;/span&gt;

&lt;span class="c"&gt;# Add certificate to macOS Keychain (macOS only)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OSTYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"darwin"&lt;/span&gt;&lt;span class="k"&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;echo&lt;/span&gt; &lt;span class="s2"&gt;"Adding certificate to macOS Keychain..."&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;security add-trusted-cert &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; trustRoot &lt;span class="nt"&gt;-k&lt;/span&gt; /Library/Keychains/System.keychain &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt;.crt"&lt;/span&gt;

  &lt;span class="c"&gt;# Add domains to /etc/hosts&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Adding domains to /etc/hosts..."&lt;/span&gt;
  &lt;span class="nv"&gt;HOSTS_ENTRY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1 &lt;/span&gt;&lt;span class="nv"&gt;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt; squidex.&lt;/span&gt;&lt;span class="nv"&gt;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; /etc/hosts&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &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;$HOSTS_ENTRY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/hosts &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Added: &lt;/span&gt;&lt;span class="nv"&gt;$HOSTS_ENTRY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &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;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt; already in /etc/hosts"&lt;/span&gt;
  &lt;span class="k"&gt;fi
fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅ Certificate created!"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Certificate: &lt;/span&gt;&lt;span class="nv"&gt;$CERT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt;.crt"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Key: &lt;/span&gt;&lt;span class="nv"&gt;$CERT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$CERT_NAME&lt;/span&gt;&lt;span class="s2"&gt;.key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the file doesn't have execute rights:&lt;br&gt;
&lt;code&gt;bash setup-certs.sh happy.dev 365&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I ran it from &lt;code&gt;.devcontainer/ssh/&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Notes for Linux users
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;host.docker.internal&lt;/code&gt; doesn't work out of the box like it does on macOS. You need to add this to the &lt;code&gt;docker-compose.yml&lt;/code&gt; under the nginx service:&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;extra_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host.docker.internal:host-gateway"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;We now have a professional-grade development setup that eliminates the need for localhost entirely. By routing traffic through a reverse proxy with valid SSL handling, we have achieved production parity on our local machines.&lt;/p&gt;

&lt;p&gt;Is this more effort than simply running a dev server on a random port? &lt;strong&gt;Yes.&lt;/strong&gt; But the trade-off is significant:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero "Magic" Code:&lt;/strong&gt; You no longer need &lt;code&gt;if (development) { ... }&lt;/code&gt; blocks to handle URLs or protocol differences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security First:&lt;/strong&gt; You catch CORS, HSTS, and SSL issues during development rather than at 2 AM during a production deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skill Mastery:&lt;/strong&gt; You've moved beyond being a "local-only" developer to understanding the networking layers that power modern web applications.&lt;/p&gt;

</description>
      <category>certificate</category>
      <category>macos</category>
      <category>devcontainer</category>
      <category>nginx</category>
    </item>
    <item>
      <title>Production Parity: Routing Local Traffic with a Reverse Proxy</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Mon, 29 Dec 2025 16:33:53 +0000</pubDate>
      <link>https://dev.to/sukkergris/production-parity-routing-local-traffic-with-a-reverse-proxy-2ndh</link>
      <guid>https://dev.to/sukkergris/production-parity-routing-local-traffic-with-a-reverse-proxy-2ndh</guid>
      <description>&lt;p&gt;When building modern web applications, it’s common to have a frontend (like a Single Page Application) and a backend API running on different ports. This setup introduces friction—especially when it comes to routing and security during local development.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Port Mismatch
&lt;/h2&gt;

&lt;p&gt;Most frontend code expects to call the backend API at a specific route, such as &lt;code&gt;/api/endpoint&lt;/code&gt;. However, locally, your backend might run on &lt;code&gt;localhost:5130&lt;/code&gt; while the frontend is served on &lt;code&gt;localhost:9876&lt;/code&gt;. This mismatch leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CORS Issues:&lt;/strong&gt; Browsers block requests between different ports for security.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment-Specific Code:&lt;/strong&gt; Needing logic to switch between local and production URLs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Onboarding Complexity:&lt;/strong&gt; New developers must manage multiple addresses and configurations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Solution: A Reverse Proxy
&lt;/h2&gt;

&lt;p&gt;A reverse proxy like Nginx acts as a single entry point. By listening on a unified port (e.g., &lt;code&gt;localhost:8314&lt;/code&gt;), it streamlines the workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Uniform Routing:&lt;/strong&gt; The frontend calls &lt;code&gt;/api/...&lt;/code&gt; regardless of the environment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transparent Forwarding:&lt;/strong&gt; Nginx intercepts &lt;code&gt;/api/&lt;/code&gt; requests and forwards them to the backend servic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local Parity:&lt;/strong&gt; Your development environment architecture closely mirrors production&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration: The Hosts File
&lt;/h2&gt;

&lt;p&gt;To use a custom domain (like &lt;code&gt;myapp.test&lt;/code&gt;), you must update your local hosts file. This tells your OS to route traffic for that domain to your local machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On macOS:&lt;/strong&gt; Edit &lt;code&gt;/etc/hosts&lt;/code&gt; using &lt;code&gt;&amp;gt;sudo nano /etc/hosts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Devcontainers:&lt;/strong&gt; Ensure your Linux-based (Debian/Alpine) containers can resolve the host.&lt;/p&gt;

&lt;p&gt;Add a line such as: &lt;code&gt;127.0.0.1 myapp.test&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Note on TLDs&lt;/strong&gt;&lt;br&gt;
Choosing the right Top-Level Domain matters:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.test&lt;/code&gt; / &lt;code&gt;.localhost&lt;/code&gt;: Recommended. These are reserved for local use and testing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.local&lt;/code&gt;: Can cause delays on macOS due to mDNS (Bonjour) conflicts.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.dev&lt;/code&gt; / &lt;code&gt;.foo&lt;/code&gt;: These are on the HSTS Preload List, meaning browsers will force HTTPS. If you use these, you must configure SSL certificates in your proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; Frontend code remains identical across all environments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplicity:&lt;/strong&gt; No more CORS headaches or complex URL configurations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automation:&lt;/strong&gt; Tools like &lt;a href="https://www.npmjs.com/package/site-config-loader" rel="noopener noreferrer"&gt;site-config-loader&lt;/a&gt; work easily with Nginx to handle configurations for the frontend&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A complete example on how I use this setup myself can be seen here: &lt;a href="https://dev.to/sukkergris/4creating-the-pinnacle-of-niche-software-abandoning-localhost1234-locally-g2g"&gt;Link!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>(3)Creating the Pinnacle of Niche Software: Using vite-plugin-elm-watch</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Mon, 22 Dec 2025 21:43:16 +0000</pubDate>
      <link>https://dev.to/sukkergris/3creating-the-pinnacle-of-niche-software-using-vite-plugin-elm-watch-36m8</link>
      <guid>https://dev.to/sukkergris/3creating-the-pinnacle-of-niche-software-using-vite-plugin-elm-watch-36m8</guid>
      <description>&lt;h2&gt;
  
  
  About
&lt;/h2&gt;

&lt;p&gt;The problem we are trying to solve here is the usual.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a stable development environment&lt;/li&gt;
&lt;li&gt;Enable Hot reloading&lt;/li&gt;
&lt;li&gt;Simplify the development process and prepare for production&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this article we will go through how to use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/ryan-haskell/vite-plugin-elm-watch" rel="noopener noreferrer"&gt;vite-plugin-elm-watch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/site-config-loader" rel="noopener noreferrer"&gt;site-config-loader&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Add tailwind css to the project&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Key takaways&lt;/strong&gt;&lt;br&gt;
A working example of this setup running in a devcontaner can be found here - &lt;a href="https://github.com/sukkergris/elm-first-vite-example/tree/feature/add-tailwind" rel="noopener noreferrer"&gt;Link&lt;/a&gt;! in the branch &lt;code&gt;feature/add-tailwind&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;host: '0.0.0.0'&lt;/code&gt; or &lt;code&gt;host: true&lt;/code&gt; is needed in order to run vite from a devcontainer - Documentation - &lt;a href="https://vite.dev/config/server-options" rel="noopener noreferrer"&gt;Link&lt;/a&gt;!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tailwindcss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tailwindcss/vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;elm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite-plugin-elm-watch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;devMetaTagPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./vite-plugin-dev-meta.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;publicDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wwwroot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;emptyOutDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;devMetaTagPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;

  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3456&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Listen on all network interfaces to allow access from the host machine&lt;/span&gt;
    &lt;span class="na"&gt;allowedHosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;host.docker.internal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  First let's setup vite to handle building Elm
&lt;/h1&gt;

&lt;p&gt;Here is the steps when starting from scratch&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;elm init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;touch src/Main.elm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm install vite-plugin-elm-watch --save-dev&lt;/code&gt; OR -D&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm install site-config-loader&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;touch index.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;! Emmet Abbreviation (To fill index.html)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;touch src/main.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add to index.html
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/src/main.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add to src/index.js
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Main&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Main.elm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;touch vite.config.mjs&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;elm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite-plugin-elm-watch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;publicDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wwwroot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;emptyOutDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;

  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3456&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Listen on all network interfaces to allow access from the host machine&lt;/span&gt;
    &lt;span class="na"&gt;allowedHosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;host.docker.internal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;code&gt;npm install vite -D&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npx vite&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Environment Management with site-config-loader
&lt;/h2&gt;

&lt;p&gt;Managing environment variables in a frontend application can be tricky. site-config-loader simplifies this by fetching configuration files based on the environment your site is running in.&lt;/p&gt;

&lt;p&gt;To determine the environment, the loader looks for a  tag. During development, we can use a custom Vite plugin to inject this tag dynamically.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create Configuration Files
&lt;/h3&gt;

&lt;p&gt;First, we need to store our environment-specific data. We’ll create a default config and a local override.&lt;/p&gt;

&lt;p&gt;Bash&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; public/config
&lt;span class="nb"&gt;touch &lt;/span&gt;public/config/environmentVariables.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;public/config/environmentVariables.local.json&lt;br&gt;
Add your variables to these files. For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;public/config/environmentVariables.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;"API_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.production.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"FEATURE_FLAG"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Inject the Environment Meta Tag
&lt;/h3&gt;

&lt;p&gt;Since we want to control which configuration the loader picks up during development, we use Vite’s transformIndexHtml hook. By default, site-config-loader might fall back to local based on the URL, but using a meta tag gives us explicit control.&lt;/p&gt;

&lt;p&gt;Create a small plugin file: &lt;code&gt;vite-plugin-dev-meta.mjs&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;devMetaTagPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Only apply this transformation during local development ('serve')&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serve&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html-transform-dev-only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;transformIndexHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;meta name="environment-name" content="local"&amp;gt;&amp;lt;/head&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Register the Plugin in Vite
&lt;/h3&gt;

&lt;p&gt;Now, integrate the plugin into your vite.config.mjs to ensure the tag is injected when you run the dev server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;elm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite-plugin-elm-watch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;devMetaTagPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./vite-plugin-dev-meta.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other config&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;devMetaTagPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Verify the Setup
&lt;/h3&gt;

&lt;p&gt;Run your development server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
Open the browser console. You should see site-config-loader successfully detecting the environment and loading the merged configuration from your JSON files.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding tailwind
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;npm install -D @tailwindcss/cli @tailwindcss/vite tailwindcss&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;touch src/site.css&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add content to site.css
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;bg-pink-50&lt;/span&gt; &lt;span class="err"&gt;text-red-900&lt;/span&gt; &lt;span class="err"&gt;font-sans;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add usage of tailwind to &lt;code&gt;vite.config.mjs&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tailwindcss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tailwindcss/vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;devMetaTagPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add the css to &lt;code&gt;main.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./site.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Restart the vite server&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Using Environment Variables in Elm
&lt;/h2&gt;

&lt;p&gt;Since &lt;code&gt;site-config-loader&lt;/code&gt; fetches your configuration asynchronously, we need to wait for the data to be ready before initializing the Elm application. We then pass the configuration into Elm using Flags.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update src/main.js
Modify your entry point to load the config first, then start Elm:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fortawesome/fontawesome-free/css/all.min.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loadEnvironmentVariables&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;site-config-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./kort-til-kort.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Main&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/src/Main.elm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clientsettings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;isDevelopment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDevelopment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;turnstileSiteKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;turnstile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sitekey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;googleConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;mapsApiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;googleConfigurations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mapsApiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;mapId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;googleConfigurations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mapId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;pixelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;facebookPixelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;There are quit a few moving parts need to get this up and going.&lt;/p&gt;

&lt;p&gt;A Complete example can be found here: &lt;a href="https://github.com/sukkergris/elm-first-vite-example" rel="noopener noreferrer"&gt;Link&lt;/a&gt;! in the branch &lt;code&gt;feature/add-tailwind&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next up we will abandon using localhost entirely and use a custom domain name for development. This will also require adding quite a few moving parts, like a reverse proxy to our &lt;code&gt;.devcontainer/docker-compose.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I wrote about it previously here: &lt;a href="https://dev.to/sukkergris/nginx-for-local-development-really-48"&gt;Link&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tailwindcss</category>
      <category>siteconfigloader</category>
      <category>elm</category>
    </item>
    <item>
      <title>(2)Creating the Pinnacle of Niche Software: The devcontainer</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Mon, 22 Dec 2025 21:31:43 +0000</pubDate>
      <link>https://dev.to/sukkergris/2creating-the-pinnacle-of-niche-software-the-devcontainer-305l</link>
      <guid>https://dev.to/sukkergris/2creating-the-pinnacle-of-niche-software-the-devcontainer-305l</guid>
      <description>&lt;h2&gt;
  
  
  The Elm development environment
&lt;/h2&gt;

&lt;p&gt;I prefer as ultra stable, shareable and reproducible an environment as possible so my preferred approach is using a &lt;a href="https://code.visualstudio.com/docs/devcontainers/containers" rel="noopener noreferrer"&gt;devcontainer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until recently I saw the .devcontainer as a intrinsically part of my project only containing the one true development container setup. Devs could use &lt;a href="https://code.visualstudio.com/docs/devcontainers/containers#_personalizing-with-dotfile-repositories" rel="noopener noreferrer"&gt;dotfiles&lt;/a&gt; to bring custom configs and setup that way.&lt;/p&gt;

&lt;p&gt;However my perspective has changed recently and now I just add a folder with my own name to the &lt;code&gt;.devcontainer&lt;/code&gt; folder and leave it up to each dev how they prefer to roll.&lt;/p&gt;

&lt;p&gt;Here I'll share my current favorite setup when developing for Elm in the &lt;a href="https://dev.to/sukkergris/creating-the-pinnacle-of-niche-software-consume-squidex-headless-cms-apis-in-elm-59i7"&gt;previously described&lt;/a&gt; tech stack. And it dose involve quite a few moving parts - so hold on!&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the base container
&lt;/h2&gt;

&lt;p&gt;The project for the base contaner - tailored for my needs - can be found here: &lt;a href="https://github.com/sukkergris/elm-development-environment-builder" rel="noopener noreferrer"&gt;elm-development-environment-builder&lt;/a&gt;&lt;br&gt;
Don't get fooled by the name - it's tailored for my needs only - but do get inspired. :)&lt;/p&gt;
&lt;h2&gt;
  
  
  The devcontainer setup
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Folder structure
&lt;/h3&gt;

&lt;p&gt;I'm considering adding &lt;code&gt;theodor&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt; but I'm undecided right now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project-root/
├── .devcontainer/
│   ├── vanilla/                 # Basic example
│   └── theodor/                 # Personal environment config
│       ├── ssl/
│       │   ├── squidex.crt
│       │   └── squidex.key
│       ├── devcontainer.json
│       ├── docker-compose.yml
│       ├── Dockerfile
│       ├── Dockerfile.omnia
│       └── nginx.conf
├── backend/
├── backup/
├── build/
├── Documentation/
├── dotnet/
└── elm/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Comments
&lt;/h3&gt;

&lt;p&gt;This setup fairly advanced!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using custom domain locally - not localhost:port!&lt;/li&gt;
&lt;li&gt;Using nginx - production like&lt;/li&gt;
&lt;li&gt;Setting environment variables using nginx and &lt;a href="https://github.com/sukkergris/site-config-loader" rel="noopener noreferrer"&gt;site-config-loader&lt;/a&gt;!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;devcontainer.json&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;"Dotnet 9/10 and Elm Dev Container (Debian + Compose)"&lt;/span&gt;&lt;span class="p"&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;---&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;configuration&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;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Point&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;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;docker-compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(relative&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;.devcontainer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;folder)&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="err"&gt;//&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;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&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;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;defined&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;docker-compose.yml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;VS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;attach&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="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="err"&gt;//&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;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;inside&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;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;files&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;mounted&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;docker-compose.yml&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;"/workspace"&lt;/span&gt;&lt;span class="p"&gt;,&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=ktk-elm-devcontainer,target=/home/container-user/.elm,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;"customizations"&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="nl"&gt;"vscode"&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="nl"&gt;"settings"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Set&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;as&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;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;terminal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(installed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Dockerfile)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"terminal.integrated.defaultProfile.linux"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zsh"&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;Optional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Neovim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;integration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;path:&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="nl"&gt;"vscode-neovim.neovimExecutablePaths.linux"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/nvim"&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;"extensions"&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;"ms-vscode-remote.remote-containers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Elmtooling.elm-ls-vscode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ms-dotnettools.csharp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"william-voyek.vscode-nginx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"vscodevim.vim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ms-dotnettools.csdevkit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"EditorConfig.EditorConfig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"humao.rest-client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"esbenp.prettier-vscode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"DotJoshJohnson.xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"streetsidesoftware.code-spell-checker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"streetsidesoftware.code-spell-checker-danish"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"bradlc.vscode-tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"kamikillerto.vscode-colorize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Ionide.Ionide-fsharp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ms-azuretools.vscode-containers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"jebbs.plantuml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"task.vscode-task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecmel.vscode-html-css"&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="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="err"&gt;//&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;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;VS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;inside&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;service&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;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;match&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;user&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;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;runs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(container-user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&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;case).&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="nl"&gt;"portsAttributes"&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="nl"&gt;"3033"&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="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Elm"&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;"5130"&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="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Backend"&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;"5140"&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="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gateway"&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;"8314"&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="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nginx"&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;"8376"&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="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Squidex"&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;"9876"&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="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Elm"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;docker-compose.yml&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="c1"&gt;# Define our main development service, let's call it 'dev'&lt;/span&gt;
  &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Instructions on how to build the image for this service&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Use the current directory (.devcontainer) as the build context&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="c1"&gt;# Specify the Dockerfile within the build context&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;

    &lt;span class="c1"&gt;# Define volumes to mount&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Mount the entire project directory (one level up from .devcontainer)&lt;/span&gt;
      &lt;span class="c1"&gt;# into the /workspace directory inside the container.&lt;/span&gt;
      &lt;span class="c1"&gt;# 'cached' optimizes mount performance on macOS/Windows.&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;../..:/workspace:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ktk-elm-devcontainer:/home/container-user/.elm&lt;/span&gt;
      &lt;span class="c1"&gt;# --- Alternative for Neovim config ---&lt;/span&gt;
      &lt;span class="c1"&gt;# Instead of COPY in Dockerfile, you could mount it here:&lt;/span&gt;
      &lt;span class="c1"&gt;# Ensure the source path '../.nvim' exists in your project root.&lt;/span&gt;
      &lt;span class="c1"&gt;# - ../.nvim:/root/.config/nvim:cached&lt;/span&gt;
         &lt;span class="c1"&gt;# --- ADD THIS LINE ---&lt;/span&gt;
      &lt;span class="c1"&gt;# Mount the project's nvim config into the root user's config dir&lt;/span&gt;
      &lt;span class="c1"&gt;# Source: ./.config/nvim (relative to this docker-compose.yml file)&lt;/span&gt;
      &lt;span class="c1"&gt;# Target: /root/.config/nvim (where nvim looks when run as root)&lt;/span&gt;
      &lt;span class="c1"&gt;# - ./.config/nvim:/root/.config/nvim:cached&lt;/span&gt;
    &lt;span class="c1"&gt;# Command to run when the container starts.&lt;/span&gt;
    &lt;span class="c1"&gt;# 'sleep infinity' keeps the container running indefinitely so VS Code can attach.&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sleep infinity&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;

    &lt;span class="c1"&gt;# Environment variables can be defined here if needed&lt;/span&gt;
    &lt;span class="c1"&gt;# environment:&lt;/span&gt;
    &lt;span class="c1"&gt;#   - DATABASE_URL=...&lt;/span&gt;

    &lt;span class="c1"&gt;# The service will run as 'root' because that's the last USER in the Dockerfile.&lt;/span&gt;
    &lt;span class="c1"&gt;# You could explicitly set 'user: root' here if desired.&lt;/span&gt;
  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ktk_nginx&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8314:80"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# - ../..:/workspace&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx.conf:/etc/nginx/nginx.conf:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./ssl:/etc/nginx/ssl:ro&lt;/span&gt;
    &lt;span class="na"&gt;extra_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host.docker.internal:host-gateway"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;squidex&lt;/span&gt;
  &lt;span class="na"&gt;mongo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mongo:6"&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;ktk_mongo_data:/data/db&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
  &lt;span class="na"&gt;squidex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;squidex/squidex:7"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8376:5000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;URLS__BASEURL=https://squidex.ktk.dk&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IDENTITY__ALLOWHTTPSCHEME=false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EVENTSTORE__MONGODB__CONFIGURATION=mongodb://mongo&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;STORE__MONGODB__CONFIGURATION=mongodb://mongo&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IDENTITY__ADMINEMAIL=sukkerfrit@gmail.com&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IDENTITY__ADMINPASSWORD=0hSoS3cret!&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ASPNETCORE_URLS=http://+:5000&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:5000/healthz"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;60s&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo&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;ktk_squidex_assets:/app/Assets&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&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;ktk-elm-devcontainer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ktk_squidex_assets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ktk_mongo_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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; isuperman/elm-devcontainer-foundation:0.1.7&lt;/span&gt;
&lt;span class="c"&gt;# Project specifics could be added here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the config for running extremely production like!&lt;br&gt;
I'm even using a custom domain for local development.&lt;br&gt;
This setup make it possible to test routing, a &lt;a href="https://blog.nginx.org/blog/rate-limiting-nginx" rel="noopener noreferrer"&gt;tarpit&lt;/a&gt; and the likes.&lt;br&gt;
nginx.conf&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;worker_processes&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;;
&lt;span class="n"&gt;events&lt;/span&gt; {
    &lt;span class="n"&gt;worker_connections&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;;
}

&lt;span class="n"&gt;http&lt;/span&gt; {
    &lt;span class="n"&gt;include&lt;/span&gt;       /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;mime&lt;/span&gt;.&lt;span class="n"&gt;types&lt;/span&gt;;
    &lt;span class="n"&gt;default_type&lt;/span&gt;  &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;octet&lt;/span&gt;-&lt;span class="n"&gt;stream&lt;/span&gt;;
    &lt;span class="n"&gt;sendfile&lt;/span&gt;        &lt;span class="n"&gt;on&lt;/span&gt;;
    &lt;span class="n"&gt;keepalive_timeout&lt;/span&gt;  &lt;span class="m"&gt;65&lt;/span&gt;;

    &lt;span class="c"&gt;# Define resolver to handle host.docker.internal
&lt;/span&gt;    &lt;span class="n"&gt;resolver&lt;/span&gt; &lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;11&lt;/span&gt;;

    &lt;span class="n"&gt;server&lt;/span&gt; {
        &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;;
        &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt; &lt;span class="n"&gt;ktk&lt;/span&gt;.&lt;span class="n"&gt;dk&lt;/span&gt;;

        &lt;span class="n"&gt;location&lt;/span&gt; /&lt;span class="n"&gt;api&lt;/span&gt;/ {
            &lt;span class="n"&gt;set&lt;/span&gt; $&lt;span class="n"&gt;backend_upstream&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;.&lt;span class="n"&gt;docker&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;:&lt;span class="m"&gt;5130&lt;/span&gt;;
            &lt;span class="n"&gt;rewrite&lt;/span&gt; ^/&lt;span class="n"&gt;api&lt;/span&gt;/(.*)$ /$&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="n"&gt;break&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://$&lt;span class="n"&gt;backend_upstream&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Upgrade&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="s1"&gt;'upgrade'&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_cache_bypass&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;

        }

        &lt;span class="n"&gt;location&lt;/span&gt; /&lt;span class="n"&gt;cms&lt;/span&gt;/ {
          &lt;span class="n"&gt;set&lt;/span&gt; $&lt;span class="n"&gt;backend_upstream&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;.&lt;span class="n"&gt;docker&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;:&lt;span class="m"&gt;5140&lt;/span&gt;;
          &lt;span class="n"&gt;rewrite&lt;/span&gt; ^/&lt;span class="n"&gt;cms&lt;/span&gt;/(.*)$ /$&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="n"&gt;break&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://$&lt;span class="n"&gt;backend_upstream&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Upgrade&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="s1"&gt;'upgrade'&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_cache_bypass&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
        }
        &lt;span class="c"&gt;# Proxy frontend requests to Elm dev server
&lt;/span&gt;        &lt;span class="n"&gt;location&lt;/span&gt; / {
            &lt;span class="n"&gt;set&lt;/span&gt; $&lt;span class="n"&gt;frontend_upstream&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;.&lt;span class="n"&gt;docker&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;:&lt;span class="m"&gt;3033&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://$&lt;span class="n"&gt;frontend_upstream&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Upgrade&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="s1"&gt;'upgrade'&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_cache_bypass&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
            &lt;span class="n"&gt;sub_filter&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/head&amp;gt;'&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;meta name="environment-name" content="local" /&amp;gt;&amp;lt;/head&amp;gt;'&lt;/span&gt;;
            &lt;span class="n"&gt;sub_filter_once&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;;
            &lt;span class="c"&gt;# Only apply sub_filter to index.html
&lt;/span&gt;            &lt;span class="c"&gt;# sub_filter_types text/html;
&lt;/span&gt;        }

        &lt;span class="n"&gt;location&lt;/span&gt; = /&lt;span class="n"&gt;robots&lt;/span&gt;.&lt;span class="n"&gt;txt&lt;/span&gt; {
            &lt;span class="n"&gt;set&lt;/span&gt; $&lt;span class="n"&gt;frontend_upstream&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;.&lt;span class="n"&gt;docker&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;:&lt;span class="m"&gt;3033&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://$&lt;span class="n"&gt;frontend_upstream&lt;/span&gt;/&lt;span class="n"&gt;robots&lt;/span&gt;.&lt;span class="n"&gt;txt&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
        }

        &lt;span class="n"&gt;location&lt;/span&gt; = /&lt;span class="n"&gt;humans&lt;/span&gt;.&lt;span class="n"&gt;txt&lt;/span&gt; {
            &lt;span class="n"&gt;set&lt;/span&gt; $&lt;span class="n"&gt;frontend_upstream&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;.&lt;span class="n"&gt;docker&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;:&lt;span class="m"&gt;3033&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://$&lt;span class="n"&gt;frontend_upstream&lt;/span&gt;/&lt;span class="n"&gt;humans&lt;/span&gt;.&lt;span class="n"&gt;txt&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
        }
    }
    &lt;span class="n"&gt;server&lt;/span&gt; {
        &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;;
        &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;squidex&lt;/span&gt;.&lt;span class="n"&gt;ktk&lt;/span&gt;.&lt;span class="n"&gt;dk&lt;/span&gt;;
        &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;301&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://$&lt;span class="n"&gt;host&lt;/span&gt;$&lt;span class="n"&gt;request_uri&lt;/span&gt;;
    }

    &lt;span class="n"&gt;server&lt;/span&gt; {
        &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;;
        &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;squidex&lt;/span&gt;.&lt;span class="n"&gt;ktk&lt;/span&gt;.&lt;span class="n"&gt;dk&lt;/span&gt; &lt;span class="n"&gt;www&lt;/span&gt;.&lt;span class="n"&gt;squidex&lt;/span&gt;.&lt;span class="n"&gt;ktk&lt;/span&gt;.&lt;span class="n"&gt;dk&lt;/span&gt;;

        &lt;span class="n"&gt;ssl_certificate&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;ssl&lt;/span&gt;/&lt;span class="n"&gt;squidex&lt;/span&gt;.&lt;span class="n"&gt;crt&lt;/span&gt;;
        &lt;span class="n"&gt;ssl_certificate_key&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;ssl&lt;/span&gt;/&lt;span class="n"&gt;squidex&lt;/span&gt;.&lt;span class="n"&gt;key&lt;/span&gt;;
        &lt;span class="n"&gt;ssl_protocols&lt;/span&gt; &lt;span class="n"&gt;TLSv1&lt;/span&gt;.&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="n"&gt;TLSv1&lt;/span&gt;.&lt;span class="m"&gt;3&lt;/span&gt;;

        &lt;span class="n"&gt;location&lt;/span&gt; / {
            &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;squidex&lt;/span&gt;:&lt;span class="m"&gt;5000&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;For&lt;/span&gt; $&lt;span class="n"&gt;proxy_add_x_forwarded_for&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;Proto&lt;/span&gt; $&lt;span class="n"&gt;scheme&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;

            &lt;span class="c"&gt;# WebSocket support
&lt;/span&gt;            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Upgrade&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="s1"&gt;'upgrade'&lt;/span&gt;;

            &lt;span class="c"&gt;# Authentication flows
&lt;/span&gt;            &lt;span class="n"&gt;proxy_cookie_path&lt;/span&gt; / /;
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Here we see an example of how one could use a devcontainer to set up a development environment. Using this approach has definitely helped me a lot. This is very production-like, and by using most of these settings for a local &lt;code&gt;docker-compose.yml&lt;/code&gt;, I am extremely confident when pushing my containers to production. To emphasize, I rarely have bugs in production due to this setup.&lt;/p&gt;

&lt;p&gt;Next up we will focus on how to setup &lt;a href="https://github.com/ryan-haskell/vite-plugin-elm-watch" rel="noopener noreferrer"&gt;vite-plugin-elm-watch&lt;/a&gt; which will require some tingling in order to run inside a devcontainer.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>tooling</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Creating the Pinnacle of Niche Software: Consume Squidex Headless CMS Api's in ELM!</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Sun, 19 Oct 2025 12:11:28 +0000</pubDate>
      <link>https://dev.to/sukkergris/creating-the-pinnacle-of-niche-software-consume-squidex-headless-cms-apis-in-elm-59i7</link>
      <guid>https://dev.to/sukkergris/creating-the-pinnacle-of-niche-software-consume-squidex-headless-cms-apis-in-elm-59i7</guid>
      <description>&lt;h2&gt;
  
  
  There is a first time for everything, I guess
&lt;/h2&gt;

&lt;p&gt;When choosing an &lt;strong&gt;extreme niche&lt;/strong&gt; programming language like Elm, you are bound to do more work since many libraries simply don't exist for Elm - yet.&lt;/p&gt;

&lt;p&gt;But the past years using Elm, I have learned more about how the web actually works than I learned the previous 20 years—and this is why.&lt;/p&gt;

&lt;p&gt;In the early days I started on the job market, I programmed using C# and Windows Forms. I wasn't involved in web development the first decade or so. When many others were using Ruby on Rails, I was building corporate WPF applications. And for a time, this was fine. But I longed for working with what at least I considered to be the future, so I acquired a role as a web developer.&lt;/p&gt;

&lt;p&gt;Back then, React was new, and &lt;strong&gt;Knockout.js&lt;/strong&gt; was still a thing. Coming from WPF, I actually liked it.&lt;/p&gt;

&lt;p&gt;Throughout my career I have worked with different frontend frameworks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Razor Pages&lt;/li&gt;
&lt;li&gt;Knockout.js&lt;/li&gt;
&lt;li&gt;React.js&lt;/li&gt;
&lt;li&gt;Angular.js&lt;/li&gt;
&lt;li&gt;Vanilla HTML + CSS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common experience has been that handling state is a pain and most of the hard stuff of programming for the web was hidden in the various frameworks.&lt;/p&gt;

&lt;p&gt;So I got a lot of stuff done, but never really got to learn the depths of the decisions the framework creators had made.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Elm!
&lt;/h2&gt;

&lt;p&gt;Over the years, I have tried to use functional programming in a few &lt;strong&gt;projects&lt;/strong&gt;. It was actually when I tried to learn &lt;a href="https://fsbolero.io/docs/" rel="noopener noreferrer"&gt;&lt;strong&gt;Bolero&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;I was first introduced&lt;/strong&gt; to Elm.&lt;/p&gt;

&lt;p&gt;In order to use &lt;strong&gt;Bolero's&lt;/strong&gt; Elmish, I thought I needed to learn Elm. So I learned Elm, and I haven't looked back!&lt;/p&gt;

&lt;p&gt;Now I finally got to understand the way of the web!&lt;/p&gt;

&lt;p&gt;When writing Elm, you need to understand how the browser works and why various decisions were made for you by other FE frameworks. It sounds harder, right? But I felt relieved. When writing Elm apps, most decisions are right there in the code. Easy to both use and extend. &lt;/p&gt;

&lt;p&gt;I do think I write more code when using Elm, but in the end, the app seems much more clear and easy to return to. Sometimes my apps run for years, and when I sometimes need to update something, it just feels much easier as opposed to other FE frameworks I use.&lt;/p&gt;

&lt;p&gt;This leads me to the present: Creating the pinnacle of niche software!&lt;/p&gt;

&lt;h2&gt;
  
  
  The tech stack of niche software
&lt;/h2&gt;

&lt;p&gt;This is actually not meant as a recommendation nor a deterrent from following this recipe, merely an observation—though I'm still using this stack today.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Elm&lt;/strong&gt; for the frontend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript&lt;/strong&gt;—Actually, not everything for the frontend can be written in Elm—so enter JavaScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Squidex&lt;/strong&gt;—Every real-life FE needs a CMS. Enter &lt;a href="https://squidex.io/" rel="noopener noreferrer"&gt;Squidex&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET&lt;/strong&gt;—Custom backend&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nginx&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/yarp/getting-started?view=aspnetcore-9.0" rel="noopener noreferrer"&gt;YARP&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Though I think this entire project could have been solved by simply using &lt;strong&gt;WordPress&lt;/strong&gt;, I know this project will grow. Therefore, I need a flexible foundation able to scale and easy to extend.&lt;/p&gt;

&lt;p&gt;In the coming articles, I'll go through the entire setup. And believe me - it won't look like anything you have ever seen before.&lt;/p&gt;

</description>
      <category>elm</category>
      <category>webdev</category>
      <category>squidex</category>
    </item>
    <item>
      <title>Using DooD in a devcontainer like a true pro!</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Sat, 23 Aug 2025 07:04:07 +0000</pubDate>
      <link>https://dev.to/sukkergris/using-dood-in-a-devcontainer-like-a-true-pro-53k0</link>
      <guid>https://dev.to/sukkergris/using-dood-in-a-devcontainer-like-a-true-pro-53k0</guid>
      <description>&lt;h2&gt;
  
  
  Yes - It is possible to use DooD in a devcontainer
&lt;/h2&gt;

&lt;p&gt;When setting up a real-world devcontainer, you often need more than just a simple “hello world” example. In practice, most development environments rely on several running containers at once — databases, caches, and supporting services. For that, it’s often easier to use Docker-out-of-Docker (DooD) so your devcontainer can talk to the host’s Docker daemon directly.&lt;/p&gt;

&lt;p&gt;In this post, we’ll set up a devcontainer environment that uses:&lt;br&gt;
    • A custom Dockerfile for building the development container.&lt;br&gt;
    • docker-compose to orchestrate the environment.&lt;br&gt;
    • DooD via the mounted Docker socket, allowing us to run other containers from inside the devcontainer.&lt;/p&gt;

&lt;p&gt;This gives you a solid foundation for building a “real-life” dev environment instead of the typical examples that don’t scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The file system&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.devcontainer
   ├── devcontainer.json   # Main devcontainer config
   ├── docker-compose.yml  # Orchestration file
   ├── Dockerfile.debian   # Custom base image for the devcontainer
   └── .env                # Environment variables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  devcontainer.json
&lt;/h3&gt;

&lt;p&gt;Here we enable the docker-outside-of-docker feature and configure environment variables:&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;"Testcontainers Debian dev-environment"&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;"/workspace"&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;"root"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"features"&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="nl"&gt;"ghcr.io/devcontainers/features/docker-outside-of-docker:1"&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="nl"&gt;"containerEnv"&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="nl"&gt;"TZ"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Europe/Copenhagen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"TESTCONTAINERS_HOST_OVERRIDE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"host.docker.internal"&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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker-compose.yml
&lt;/h3&gt;

&lt;p&gt;The compose file mounts the workspace and the host’s Docker socket:&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;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.debian&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;..:/workspace:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sleep"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;infinity"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dockerfile.debian
&lt;/h3&gt;

&lt;p&gt;We start from a slim .NET SDK base and install the necessary tooling (and some..):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Use a base Debian image
FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim

# Set noninteractive mode to avoid prompts during package installation
ENV DEBIAN_FRONTEND=noninteractive

# Create docker group and add root to it
RUN groupadd -r docker &amp;amp;&amp;amp; usermod -aG docker root

# Install dependencies
# Added ca-certificates as it's often needed for https downloads and the dotnet install script might rely on it
RUN apt-get update &amp;amp;&amp;amp; \
    apt-get install -y \
        git \
        dos2unix \
        docker.io \ &amp;lt;-- MINIMUM
        stow \
        zsh \
        tree \
        clang \
        jq \
        unzip \
        xclip \
        wget \
        curl \
        ca-certificates \
        apt-transport-https \
        software-properties-common &amp;amp;&amp;amp; \
        rm -rf /var/lib/apt/lists/*

RUN dotnet tool install Nuke.GlobalTool --global
ENV PATH="$PATH:~/.dotnet/tools"


# Install .NET Runtime using the official script.
# This script handles architecture detection for you.
RUN curl -L https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh &amp;amp;&amp;amp; \
    chmod +x /tmp/dotnet-install.sh &amp;amp;&amp;amp; \
    /tmp/dotnet-install.sh --version 9.0.4 --runtime dotnet &amp;amp;&amp;amp; \
    rm /tmp/dotnet-install.sh

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the container and test that you have access to your host's docer environment:&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%2Fb7yjxlseqm1tszm0quxw.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%2Fb7yjxlseqm1tszm0quxw.png" alt=" " width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  That's it!
&lt;/h3&gt;

&lt;p&gt;You now have a development environment capable using DooD and eg. running devcontainers like a true pro!&lt;/p&gt;

&lt;h3&gt;
  
  
  Next steps
&lt;/h3&gt;

&lt;p&gt;Add your own services to the &lt;code&gt;docker-compose.yml&lt;/code&gt; and extend the &lt;code&gt;Docker.debian&lt;/code&gt; with your own installs :)&lt;/p&gt;

</description>
      <category>docker</category>
      <category>testcontainer</category>
      <category>csharp</category>
      <category>devcontainer</category>
    </item>
    <item>
      <title>Don’t use Alpine as a dev machine when running a devcontainer - trust me bro!</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Sat, 09 Aug 2025 14:52:17 +0000</pubDate>
      <link>https://dev.to/sukkergris/dont-use-alpine-as-a-dev-machine-when-running-a-devcontainer-trust-me-bro-1dea</link>
      <guid>https://dev.to/sukkergris/dont-use-alpine-as-a-dev-machine-when-running-a-devcontainer-trust-me-bro-1dea</guid>
      <description>&lt;p&gt;Lesson learned the hard way: Don't waste time getting this up an running in an Alpine container. &lt;br&gt;
At least I encountered these errors:&lt;br&gt;
Absolutely no output from &lt;code&gt;csharp-lsp&lt;/code&gt; and since alpine uses &lt;code&gt;musl libc&lt;/code&gt; which weren't compatible with &lt;code&gt;glibc&lt;/code&gt; I gave up on using Alpine as a development machine.&lt;/p&gt;

&lt;p&gt;But using Debian (mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim) got me to the finish line.&lt;/p&gt;

</description>
      <category>mac</category>
      <category>devcontainer</category>
      <category>docker</category>
      <category>neovim</category>
    </item>
    <item>
      <title>Getting started with Squidex - the big picture</title>
      <dc:creator>Theodor Heiselberg</dc:creator>
      <pubDate>Fri, 01 Aug 2025 17:54:34 +0000</pubDate>
      <link>https://dev.to/sukkergris/getting-started-with-squidex-2oag</link>
      <guid>https://dev.to/sukkergris/getting-started-with-squidex-2oag</guid>
      <description>&lt;h2&gt;
  
  
  The Big Picture: The Philosophy Behind Our Setup
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Disclaimer: Only tested on Mac Silicon&lt;/code&gt;&lt;br&gt;
If you're ready to integrate a headless CMS into your project, you're in the right place. We're going to build a solid foundation for local development, and we'll do it by keeping our environment as close to production as possible.&lt;/p&gt;

&lt;p&gt;My projects consistently use NGINX—even for local development. For my reasoning, you can read more here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/sukkergris/nginx-for-local-development-really-48"&gt;Nginx for Local Development: Really?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/sukkergris/the-red-pill-of-software-delivery-unmasking-magic-code-and-building-for-reality-1ng9"&gt;The Red Pill of Software Delivery: Unmasking Magic Code and Building for Reality&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The gist of it is this: I'll be showcasing how to set up a development environment for a Single Page Application (SPA) and a backend running in a devcontainer, using Squidex as our headless CMS.&lt;/p&gt;

&lt;p&gt;To keep this environment close to a real-world scenario, we'll be using local domain names. This means we'll have one domain for our main frontend (the SPA) and another for the Squidex administration app, which is also a SPA.&lt;/p&gt;

&lt;p&gt;This approach gives us a reliable, consistent, and realistic development setup that will make our transition to production much smoother.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Development Environment
&lt;/h2&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%2Flwv2f99jreb9fceh2cnf.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%2Flwv2f99jreb9fceh2cnf.png" alt=" " width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see overview we will be routing all of our development server endpoints though Nginx.&lt;/p&gt;

&lt;p&gt;Let's start building our devcontainer environment!&lt;/p&gt;
&lt;h2&gt;
  
  
  The devcontainer
&lt;/h2&gt;

&lt;p&gt;Since this post is all about how to get started using Squidex I won't be covering much about how to setup a devcontainer.&lt;/p&gt;

&lt;p&gt;.devcontainer/devcontainer.json&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;"Dotnet 9 and Elm Dev Container (Alpine + Compose)"&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;"/workspace"&lt;/span&gt;&lt;span class="p"&gt;,&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=my-app-elm-devcontainer,target=/home/container-user/.elm,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;"customizations"&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="nl"&gt;"vscode"&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="nl"&gt;"settings"&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="nl"&gt;"terminal.integrated.defaultProfile.linux"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zsh"&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;"extensions"&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;"ms-vscode-remote.remote-containers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Elmtooling.elm-ls-vscode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ms-dotnettools.csharp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"william-voyek.vscode-nginx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"vscodevim.vim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ms-dotnettools.csdevkit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"EditorConfig.EditorConfig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"humao.rest-client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"esbenp.prettier-vscode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"DotJoshJohnson.xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"streetsidesoftware.code-spell-checker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"streetsidesoftware.code-spell-checker-danish"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"bradlc.vscode-tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"kamikillerto.vscode-colorize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Ionide.Ionide-fsharp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ms-azuretools.vscode-containers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"jebbs.plantuml"&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="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="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="w"&gt;
&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;p&gt;Dockerfile.omnia&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="c"&gt;# Use a stable version of Alpine as the base image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.21.0&lt;/span&gt;

&lt;span class="c"&gt;# Set up the working directory&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /workspace&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; HOME_DIR="/home/container-user"&lt;/span&gt;
&lt;span class="c"&gt;# ENV LV_BRANCH="release-1.4/neovim-0.9"&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="$PATH:$HOME_DIR/.local/bin"&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    python3 &lt;span class="se"&gt;\
&lt;/span&gt;    py3-pip &lt;span class="se"&gt;\
&lt;/span&gt;    gcc &lt;span class="se"&gt;\
&lt;/span&gt;    musl-dev &lt;span class="se"&gt;\
&lt;/span&gt;    python3-dev &lt;span class="se"&gt;\
&lt;/span&gt;    libffi-dev &lt;span class="se"&gt;\
&lt;/span&gt;    openssl-dev &lt;span class="se"&gt;\
&lt;/span&gt;    cargo &lt;span class="se"&gt;\
&lt;/span&gt;    make &lt;span class="se"&gt;\
&lt;/span&gt;    yarn &lt;span class="se"&gt;\
&lt;/span&gt;    git &lt;span class="se"&gt;\
&lt;/span&gt;    openssh &lt;span class="se"&gt;\
&lt;/span&gt;    neovim &lt;span class="se"&gt;\
&lt;/span&gt;    neovim-doc &lt;span class="se"&gt;\
&lt;/span&gt;    xclip &lt;span class="se"&gt;\
&lt;/span&gt;    ripgrep &lt;span class="se"&gt;\
&lt;/span&gt;    alpine-sdk &lt;span class="se"&gt;\
&lt;/span&gt;    dotnet9-sdk &lt;span class="se"&gt;\
&lt;/span&gt;    bash &lt;span class="se"&gt;\
&lt;/span&gt;    zsh &lt;span class="se"&gt;\
&lt;/span&gt;    tree &lt;span class="se"&gt;\
&lt;/span&gt;    stow &lt;span class="se"&gt;\
&lt;/span&gt;    unzip &lt;span class="se"&gt;\
&lt;/span&gt;    nushell &lt;span class="se"&gt;\
&lt;/span&gt;    tmux &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    jq &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="se"&gt;\
&lt;/span&gt;    nodejs &lt;span class="se"&gt;\
&lt;/span&gt;    npm &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    libc6-compat  &lt;span class="c"&gt;# Install if needed for compatibility with Elm binaries&lt;/span&gt;

&lt;span class="c"&gt;# Configuring Elm version&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; ELM_VERSION=latest-0.19.1&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; ELM_TEST_VERSION=latest-0.19.1&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; ELM_FORMAT_VERSION=latest-0.19.1&lt;/span&gt;

&lt;span class="c"&gt;# This Dockerfile adds a non-root user with sudo access.&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; USERNAME=container-user&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=$USER_UID&lt;/span&gt;

&lt;span class="c"&gt;# Add a non-root user and group&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&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;    adduser &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nv"&gt;$USERNAME&lt;/span&gt; &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="nv"&gt;$USERNAME&lt;/span&gt; &lt;span class="nt"&gt;--shell&lt;/span&gt; /bin/sh

&lt;span class="c"&gt;# Install Elm using the provided method, elm-test and elm-format via npm&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="c"&gt;# Install Elm binary&lt;/span&gt;
    curl -L -o elm.gz https://github.com/elm/compiler/releases/download/0.19.1/binary-for-linux-64-bit.gz &amp;amp;&amp;amp; \
    gunzip elm.gz &amp;amp;&amp;amp; \
    chmod +x elm &amp;amp;&amp;amp; \
    mv elm /usr/local/bin/elm &amp;amp;&amp;amp; \
    # Install elm-test and elm-format via npm
    npm install --global \
        elm-test@${ELM_TEST_VERSION} \
        elm-format@${ELM_FORMAT_VERSION} \
        elm-watch@beta &amp;amp;&amp;amp; \
    # [Optional] Update UID/GID if needed
    if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then \
        groupmod --gid $USER_GID $USERNAME &amp;amp;&amp;amp; \
        usermod --uid $USER_UID --gid $USER_GID $USERNAME &amp;amp;&amp;amp; \
        chown -R $USER_UID:$USER_GID /home/$USERNAME; \
    fi &amp;amp;&amp;amp; \
    # Create the elm cache directory where we can mount a volume
    mkdir /home/$USERNAME/.elm &amp;amp;&amp;amp; \
    chown $USERNAME:$USERNAME /home/$USERNAME/.elm &amp;amp;&amp;amp; \
    # Create the .azure directory
    mkdir -p /home/container-user/.azure &amp;amp;&amp;amp; \
    chown $USERNAME:$USERNAME /home/$USERNAME/.azure

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; ENV=/home/$USERNAME/.profile&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; $USERNAME&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 https://github.com/romkatv/powerlevel10k.git /home/&lt;span class="nv"&gt;$USERNAME&lt;/span&gt;/.oh-my-zsh/custom/themes/powerlevel10k

&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; csharp-ls
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="$PATH:~/.dotnet/tools"&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/opt/venv/bin:$PATH"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;docker-compose.yml&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;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.omnia&lt;/span&gt;

    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# 'cached' optimizes mount performance on macOS/Windows.&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;../..:/workspace:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;my-app-elm-devcontainer:/home/container-user/.elm&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sleep infinity&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;
  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app_nginx&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8314:80"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&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;./nginx.conf:/etc/nginx/nginx.conf:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./ssl:/etc/nginx/ssl:ro&lt;/span&gt;
    &lt;span class="na"&gt;extra_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host.docker.internal:host-gateway"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;squidex&lt;/span&gt;
  &lt;span class="na"&gt;mongo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mongo:6"&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;my-app_mongo_data:/data/db&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
  &lt;span class="na"&gt;squidex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;squidex/squidex:7"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8376:5000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;URLS__BASEURL=https://squidex.my-app.dk&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IDENTITY__ALLOWHTTPSCHEME=false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EVENTSTORE__MONGODB__CONFIGURATION=mongodb://mongo&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;STORE__MONGODB__CONFIGURATION=mongodb://mongo&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IDENTITY__ADMINEMAIL=sukkerfrit@gmail.com&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IDENTITY__ADMINPASSWORD=Lucas2007!&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ASPNETCORE_URLS=http://+:5000&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:5000/healthz"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;60s&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo&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;my-app_squidex_assets:/app/Assets&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&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;my-app-elm-devcontainer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;my-app_squidex_assets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;my-app_mongo_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now for the quite important nginx.conf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;worker_processes&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;;
&lt;span class="n"&gt;events&lt;/span&gt; {
    &lt;span class="n"&gt;worker_connections&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;;
}

&lt;span class="n"&gt;http&lt;/span&gt; {
    &lt;span class="n"&gt;include&lt;/span&gt;       /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;mime&lt;/span&gt;.&lt;span class="n"&gt;types&lt;/span&gt;;
    &lt;span class="n"&gt;default_type&lt;/span&gt;  &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;octet&lt;/span&gt;-&lt;span class="n"&gt;stream&lt;/span&gt;;
    &lt;span class="n"&gt;sendfile&lt;/span&gt;        &lt;span class="n"&gt;on&lt;/span&gt;;
    &lt;span class="n"&gt;keepalive_timeout&lt;/span&gt;  &lt;span class="m"&gt;65&lt;/span&gt;;

    &lt;span class="c"&gt;# Define resolver to handle host.docker.internal
&lt;/span&gt;    &lt;span class="n"&gt;resolver&lt;/span&gt; &lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;11&lt;/span&gt;;

    &lt;span class="n"&gt;server&lt;/span&gt; {
        &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;;
        &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt; &lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;dk&lt;/span&gt;;

        &lt;span class="n"&gt;location&lt;/span&gt; /&lt;span class="n"&gt;api&lt;/span&gt;/ {
            &lt;span class="n"&gt;set&lt;/span&gt; $&lt;span class="n"&gt;backend_upstream&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;.&lt;span class="n"&gt;docker&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;:&lt;span class="m"&gt;5130&lt;/span&gt;;
            &lt;span class="n"&gt;rewrite&lt;/span&gt; ^/&lt;span class="n"&gt;api&lt;/span&gt;/(.*)$ /$&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="n"&gt;break&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://$&lt;span class="n"&gt;backend_upstream&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Upgrade&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="s1"&gt;'upgrade'&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_cache_bypass&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
        }

        &lt;span class="n"&gt;location&lt;/span&gt; /&lt;span class="n"&gt;cms&lt;/span&gt;/ {
          &lt;span class="n"&gt;set&lt;/span&gt; $&lt;span class="n"&gt;backend_upstream&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;.&lt;span class="n"&gt;docker&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;:&lt;span class="m"&gt;5140&lt;/span&gt;;
          &lt;span class="n"&gt;rewrite&lt;/span&gt; ^/&lt;span class="n"&gt;cms&lt;/span&gt;/(.*)$ /$&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="n"&gt;break&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://$&lt;span class="n"&gt;backend_upstream&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Upgrade&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="s1"&gt;'upgrade'&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_cache_bypass&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
        }
        &lt;span class="c"&gt;# Proxy frontend requests to Elm dev server
&lt;/span&gt;        &lt;span class="n"&gt;location&lt;/span&gt; / {
            &lt;span class="n"&gt;set&lt;/span&gt; $&lt;span class="n"&gt;frontend_upstream&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;.&lt;span class="n"&gt;docker&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;:&lt;span class="m"&gt;3033&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://$&lt;span class="n"&gt;frontend_upstream&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Upgrade&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="s1"&gt;'upgrade'&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_cache_bypass&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
            &lt;span class="n"&gt;sub_filter&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/head&amp;gt;'&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;meta name="environment-name" content="local" /&amp;gt;&amp;lt;/head&amp;gt;'&lt;/span&gt;;
            &lt;span class="n"&gt;sub_filter_once&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;;
            &lt;span class="c"&gt;# Only apply sub_filter to index.html
&lt;/span&gt;            &lt;span class="c"&gt;# sub_filter_types text/html;
&lt;/span&gt;        }

        &lt;span class="n"&gt;location&lt;/span&gt; = /&lt;span class="n"&gt;robots&lt;/span&gt;.&lt;span class="n"&gt;txt&lt;/span&gt; {
            &lt;span class="n"&gt;set&lt;/span&gt; $&lt;span class="n"&gt;frontend_upstream&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;.&lt;span class="n"&gt;docker&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;:&lt;span class="m"&gt;3033&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://$&lt;span class="n"&gt;frontend_upstream&lt;/span&gt;/&lt;span class="n"&gt;robots&lt;/span&gt;.&lt;span class="n"&gt;txt&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
        }

        &lt;span class="n"&gt;location&lt;/span&gt; = /&lt;span class="n"&gt;humans&lt;/span&gt;.&lt;span class="n"&gt;txt&lt;/span&gt; {
            &lt;span class="n"&gt;set&lt;/span&gt; $&lt;span class="n"&gt;frontend_upstream&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;.&lt;span class="n"&gt;docker&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;:&lt;span class="m"&gt;3033&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://$&lt;span class="n"&gt;frontend_upstream&lt;/span&gt;/&lt;span class="n"&gt;humans&lt;/span&gt;.&lt;span class="n"&gt;txt&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
        }
    }
    &lt;span class="n"&gt;server&lt;/span&gt; {
        &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;;
        &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;squidex&lt;/span&gt;.&lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;dk&lt;/span&gt;;
        &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;301&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://$&lt;span class="n"&gt;host&lt;/span&gt;$&lt;span class="n"&gt;request_uri&lt;/span&gt;;
    }

    &lt;span class="n"&gt;server&lt;/span&gt; {
        &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;;
        &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;squidex&lt;/span&gt;.&lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;dk&lt;/span&gt;;

        &lt;span class="n"&gt;ssl_certificate&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;ssl&lt;/span&gt;/&lt;span class="n"&gt;squidex&lt;/span&gt;.&lt;span class="n"&gt;crt&lt;/span&gt;;
        &lt;span class="n"&gt;ssl_certificate_key&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;ssl&lt;/span&gt;/&lt;span class="n"&gt;squidex&lt;/span&gt;.&lt;span class="n"&gt;key&lt;/span&gt;;
        &lt;span class="n"&gt;ssl_protocols&lt;/span&gt; &lt;span class="n"&gt;TLSv1&lt;/span&gt;.&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="n"&gt;TLSv1&lt;/span&gt;.&lt;span class="m"&gt;3&lt;/span&gt;;

        &lt;span class="n"&gt;location&lt;/span&gt; / {
            &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;squidex&lt;/span&gt;:&lt;span class="m"&gt;5000&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;For&lt;/span&gt; $&lt;span class="n"&gt;proxy_add_x_forwarded_for&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;Proto&lt;/span&gt; $&lt;span class="n"&gt;scheme&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;

            &lt;span class="c"&gt;# WebSocket support
&lt;/span&gt;            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Upgrade&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
            &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="s1"&gt;'upgrade'&lt;/span&gt;;

            &lt;span class="c"&gt;# Authentication flows
&lt;/span&gt;            &lt;span class="n"&gt;proxy_cookie_path&lt;/span&gt; / /;
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  We are not done yet!
&lt;/h2&gt;

&lt;p&gt;You'll need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create your own certs (.devcontainer/ssl/...)&lt;/li&gt;
&lt;li&gt;Update your hosts file (/ect/hosts)&lt;/li&gt;
&lt;li&gt;Remove or add your own versions of the app's for &lt;code&gt;/api/&lt;/code&gt; and &lt;code&gt;/cms/&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This should get you up and running with the basics of Squidex!&lt;/p&gt;

&lt;h2&gt;
  
  
  Security &lt;code&gt;/cms/&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I just create a wrapper/proxy for the Squidex api. This is done in order to keep the Client Secret - well secret. In production I use the role Reader for public stuff like Blog's, faq's etc.&lt;br&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%2Fo2h4y6vz51s7kifuljs6.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%2Fo2h4y6vz51s7kifuljs6.png" alt=" " width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want the code for the proxy/wrapper do write me.&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing the setup with custom domain name
&lt;/h2&gt;

&lt;p&gt;From your host machine eg. run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-v&lt;/span&gt; http://my-app.xyz/cms/proxy/api/content/my-app/blog

curl &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; https://squidex.my-app.xyz/squidex/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tust me bro!
&lt;/h2&gt;

&lt;p&gt;Make your host machines browsers trust your self-signed cert for the test domain name&lt;/p&gt;

&lt;p&gt;On Mac:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Keychain Access&lt;/li&gt;
&lt;li&gt;I used 'login' as Default Keychain&lt;/li&gt;
&lt;li&gt;File -&amp;gt; Import Items&lt;/li&gt;
&lt;li&gt;Import the created cert&lt;/li&gt;
&lt;li&gt;Set the trust level (Double click on the file)&lt;/li&gt;
&lt;/ol&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%2F56e75q0738emd64tcqfa.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%2F56e75q0738emd64tcqfa.png" alt=" " width="770" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing for Production
&lt;/h2&gt;

&lt;p&gt;When we have published all our containers we are ready to create yet another docker-compose file. This one is for testing the entire system as production like as possible.&lt;br&gt;
That means we will be setting up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A completely new &lt;code&gt;docker-compose.my-app.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A completely revised &lt;code&gt;nginx.conf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create new a certificate just for this test (Could be omitted)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create a Certificate for multiple domain names
&lt;/h2&gt;

&lt;p&gt;.... Coming soon ....&lt;/p&gt;

</description>
      <category>squidex</category>
      <category>docker</category>
      <category>nginx</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
