<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Entre Compétents blog Blog</title>
        <link>https://entrecompetents.fr/blog/</link>
        <description>Entre Compétents blog Blog</description>
        <lastBuildDate>Mon, 09 Feb 2026 19:25:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[How i have integrated react-flow with complex deep nested object data manipuation]]></title>
            <link>https://entrecompetents.fr/blog/theStackBehindComposeCraftNext</link>
            <guid>https://entrecompetents.fr/blog/theStackBehindComposeCraftNext</guid>
            <pubDate>Mon, 09 Feb 2026 19:25:36 GMT</pubDate>
            <description><![CDATA[How i have integrated react-flow with complex deep nested object data manipuation]]></description>
            <content:encoded><![CDATA[<p>This article is the following of <a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft">the article "How to create a nextjs + mongoDB SaaS fully self-hosted"</a></p>
<p>We will mainly focus on how does the magic happen inside the NextJs App ?</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-requirements-">The requirements :<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraftNext#the-requirements-" class="hash-link" aria-label="Direct link to The requirements :" title="Direct link to The requirements :">​</a></h2>
<p>What we want to build is a nodal GUI handler for docker-compose data handling.</p>
<p>From this product need we can extract somes technicals answers :</p>
<ul>
<li>We need a good data handling of docker-compose</li>
<li>We need a tool to help us with the nodal GUI</li>
<li>We need a state management sytem that allow us complex deep nested object mutations, in an efficient way.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="choosing-a-data-library-to-handle-docker-compose-efficiently">Choosing a data library to handle docker-compose efficiently<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraftNext#choosing-a-data-library-to-handle-docker-compose-efficiently" class="hash-link" aria-label="Direct link to Choosing a data library to handle docker-compose efficiently" title="Direct link to Choosing a data library to handle docker-compose efficiently">​</a></h2>
<p>Docker-compose is a really complex data structre, as it's deeply nested and somes bottoms objects are linked to top ones.
I looked for any docker-compose but didn't find any in TS, so i builded mine from the ground.</p>
<p>It's curently not open-source but we do have <a href="https://form.composecraft.com/s/cm472o6vf000qwl0z6xs9zclh" target="_blank" rel="noopener noreferrer">a form to ask acces to it</a></p>
<p>The library work with holding all the states inside a big <code>Compose</code> class.
managing the state of just one <code>Compose</code> instance, it becomes straightforward to stay up-to-date with all job-related data.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="react-state-management">React state management<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraftNext#react-state-management" class="hash-link" aria-label="Direct link to React state management" title="Direct link to React state management">​</a></h3>
<p>From the start we knew we have to manage the <code>Compose</code> state on client side as there is a lot of state mutation made on the playground page.</p>
<p>But, what to choose between React UseState, Zustand, Recoil, Redux... ?</p>
<p>We chose Zustand for our state management because of its seamless integration with Next.js and its ability to modify state by passing a function instead of a new object.</p>
<p>Why does this matter?
In the traditional approach, using setData(newData) would recreate the entire Compose object on every state update, like this:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">compose</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">setCompose</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useComposeState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">setCompose</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Compose</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">whatever you change</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This would result in a new instance of Compose every time, which could lead to inefficiencies or unnecessary re-renders.</p>
<p>Instead, Zustand allows us to pass a modifier function, enabling changes to be applied directly to the existing object without creating a new instance:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">compose</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">setCompose</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useComposeState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">setCompose</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">oldCompose</span><span class="token punctuation" style="color:#393A34">)</span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain">oldCompose</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">whateverChange</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">anyParamsNeeded</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This approach allows targeted updates and keeps the instance intact. For more granular changes, you can directly modify properties within the state:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">compose</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">setCompose</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useComposeState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">setCompose</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">oldCompose</span><span class="token punctuation" style="color:#393A34">)</span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    oldCompose</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">service1</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"new name"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>By using this method, we maintain the simplicity and efficiency of state updates without compromising the integrity of the Compose instance</p>
<p>But you maybe wonder, what does the zustand store look like ?</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">"use client"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> create </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"zustand"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> Compose </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@entrecompetents/composecraftLib"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> generateRandomName </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/lib/utils"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">ComposeState</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    compose</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Compose</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">setCompose</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token function-variable function" style="color:#d73a49">updater</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">currentCompose</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Compose</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> useComposeStore </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">create</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">ComposeState</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">set</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    compose</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Compose</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateRandomName</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">setCompose</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token function-variable function" style="color:#d73a49">updater</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">currentCompose</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Compose</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">set</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">state</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token function" style="color:#d73a49">updater</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">state</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">compose</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> compose</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> state</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">compose </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-nodal-gui-library">The nodal GUI library<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraftNext#the-nodal-gui-library" class="hash-link" aria-label="Direct link to The nodal GUI library" title="Direct link to The nodal GUI library">​</a></h2>
<p>The options was : <a href="https://www.jointjs.com/" target="_blank" rel="noopener noreferrer">JointJS</a>, <a href="https://reactflow.dev/" target="_blank" rel="noopener noreferrer">react flow</a>, <a href="http://mermaid.js.org/" target="_blank" rel="noopener noreferrer">mermaidJS</a> (their was maybe other options but we didn't go further in the searchs)</p>
<p>Here is a small recap table of our choose :</p>
<table><thead><tr><th></th><th>JointJS</th><th>React Flow</th><th>mermaidJS</th></tr></thead><tbody><tr><td>Interactivity</td><td>✅</td><td>✅</td><td>❌</td></tr><tr><td>Easy to dev</td><td>⭐️</td><td>?</td><td>⭐️⭐️⭐️</td></tr><tr><td>Open source</td><td>✅</td><td>❌</td><td>✅</td></tr></tbody></table>
<p>We finally choosed React flow for the interactivity and the fact it's open source.</p>
<p>But keep in mind it's not an easy way to go, as react-flow require a lot of custom dev.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-playground-scheme-">The playground scheme :<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraftNext#the-playground-scheme-" class="hash-link" aria-label="Direct link to The playground scheme :" title="Direct link to The playground scheme :">​</a></h2>
<p><img decoding="async" loading="lazy" alt="scheme" src="https://entrecompetents.fr/blog/assets/images/howDoesItWorkInNextJS-18035ee61c509f5f56558acc95970552.png" width="1471" height="972" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion-">Conclusion :<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraftNext#conclusion-" class="hash-link" aria-label="Direct link to Conclusion :" title="Direct link to Conclusion :">​</a></h2>
<p>Building a nodal GUI for handling Docker Compose data in a Next.js application is a multi-layered process that combines efficient state management, a robust GUI library, and custom-built tooling. Through this exploration, we have:</p>
<ol>
<li><strong>Crafted a TypeScript library</strong> for managing Docker Compose data that suits the complexity and nested nature of the structure.</li>
<li><strong>Optimized client-side state management</strong> using Zustand, allowing for efficient updates without recreating entire objects or instances.</li>
<li><strong>Selected React Flow</strong> as the nodal GUI library, leveraging its interactivity and customizability while tackling the steep learning curve through targeted development.</li>
</ol>
<p>This integration, represented in the final scheme, showcases a streamlined workflow where deeply nested state changes, GUI interactions, and backend communication seamlessly come together. The chosen tools not only meet the functional requirements but also align with performance, usability, and scalability goals.</p>
<p>These efforts culminate in a playground page that delivers a cohesive, interactive user experience while adhering to technical best practices.</p>
<!-- -->
<iframe width="540" height="705" src="https://23a11111.sibforms.com/serve/MUIFAD4V-sjfjj3lAophYwHVb-SESnxq_HWg-plzTaW8rgGeG-yPYbFI8KeEg39tUVNQ0tVKOF2eoSlFHchztAiI2XzH7XA-UDWaIG5-vEuh7julQgBEdrhXjqYn4v46O7UC1yPmaf2cjB_H-wmJKE-QwzAgO3ct-Vq5KanudcV5GMHfGH9Z5mg6j0wa44i9vdecdOB32qdMHcFu" frameborder="0" scrolling="auto" style="display:block;margin-left:auto;margin-right:auto;max-width:100%"></iframe>]]></content:encoded>
            <category>compose craft</category>
            <category>dev</category>
        </item>
        <item>
            <title><![CDATA[How to use Traefik v3 with Docker compose swarm mode]]></title>
            <link>https://entrecompetents.fr/blog/traefik-v3-docker</link>
            <guid>https://entrecompetents.fr/blog/traefik-v3-docker</guid>
            <pubDate>Mon, 09 Feb 2026 19:25:36 GMT</pubDate>
            <description><![CDATA[The recent release of Traefik v3 brings a lot of new features. This article explains how to use Traefik v3 with Docker compose in swarm mode.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="debug api avec wireshark" src="https://entrecompetents.fr/blog/assets/images/traefik-v3-docker-ff3c5ea81071bf768655fc0f78d572cd.png" width="1200" height="480" class="img_ev3q"></p>
<p>The recent release of Traefik v3 brings a lot of new features. This article does not aim to explain all the new features of Traefik v3, but to show you how to use it with Docker compose.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="prerequisites">Prerequisites<a href="https://entrecompetents.fr/blog/traefik-v3-docker#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites">​</a></h2>
<p>Before starting, you need to have Docker and Docker compose installed on your machine. If you don't have them installed, you can follow <a href="https://docs.docker.com/engine/install/" target="_blank" rel="noopener noreferrer">the official documentation</a> to install them.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="create-a-docker-compose-file">Create a Docker compose file<a href="https://entrecompetents.fr/blog/traefik-v3-docker#create-a-docker-compose-file" class="hash-link" aria-label="Direct link to Create a Docker compose file" title="Direct link to Create a Docker compose file">​</a></h2>
<p>Let's creat a simple docker-compose.yml file to deploy a web server and Traefik v3.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">version</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'3.8'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">services</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">traefik</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> traefik</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">v3.0</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">networks</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> web</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">ports</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">target</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">80</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">published</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">80</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">protocol</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> tcp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">mode</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> host</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">target</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">443</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">published</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">443</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">protocol</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> tcp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">mode</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> host</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">environment</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> TZ=Europe/Paris</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">command</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">providers.swarm.endpoint=unix</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">///var/run/docker.sock</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">providers.docker.exposedbydefault=false</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">providers.swarm.network=fdp_web</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">accesslog</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">entryPoints.websecure.address=</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">443</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--certificatesresolvers.myresolver.acme.tlschallenge=true"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">#- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--certificatesresolvers.myresolver.acme.email=organisation@lesfousdupeloton.com"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--entryPoints.web.forwardedHeaders.trustedIPs=10.0.0.0/24,10.0.2.0/24,192.168.100.0/24"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--entryPoints.websecure.forwardedHeaders.trustedIPs=10.0.0.2/24,10.0.2.0/24,192.168.100.0/24"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">accesslog.filepath=/log/acces/access.log</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.http.middlewares.exclude-ip-log.ipwhitelist.sourceRange=217.72.195.109"</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># IP address to exclude for ping</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> ./certs</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">/letsencrypt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> /var/run/docker.sock</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">/var/run/docker.sock</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">ro</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> ./log</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">/log</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">deploy</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">placement</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">constraints</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> node.role == manager</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">restart_policy</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">condition</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> on</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">failure</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">delay</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 5s</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">max_attempts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">window</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 120s</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">labels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.enable=false"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">frontend</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> registry.gitlab.com/entrecompetents/lesfousdupeltoton/frontend</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">1.0.8</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">hostname</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> frontend.lfdp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">networks</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> web</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">deploy</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">mode</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> replicated</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">replicas</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">labels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.enable=true"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> traefik.http.services.frontend.loadbalancer.server.port=8080</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> traefik.http.routers.frontend.rule=Host(`lesfousdupeloton.com`)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.http.routers.frontend.entrypoints=websecure"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.http.routers.frontend.tls.certresolver=myresolver"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">restart_policy</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">condition</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> on</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">failure</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">delay</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 5s</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">max_attempts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">window</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 120s</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">healthcheck</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">test</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"CMD"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"curl"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--fail"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"http://localhost:8080/"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Health check command using curl</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">interval</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 5s  </span><span class="token comment" style="color:#999988;font-style:italic"># Health check interval</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">timeout</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 2s    </span><span class="token comment" style="color:#999988;font-style:italic"># Timeout for the health check</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">retries</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token plain">     </span><span class="token comment" style="color:#999988;font-style:italic"># Number of retries before marking the container as unhealthy</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">start_period</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 10s  </span><span class="token comment" style="color:#999988;font-style:italic"># Delay before starting the health checks after the container is started</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">networks</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  web</span><span class="token punctuation" style="color:#393A34">:</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<!-- -->
<iframe width="540" height="705" src="https://23a11111.sibforms.com/serve/MUIFAD4V-sjfjj3lAophYwHVb-SESnxq_HWg-plzTaW8rgGeG-yPYbFI8KeEg39tUVNQ0tVKOF2eoSlFHchztAiI2XzH7XA-UDWaIG5-vEuh7julQgBEdrhXjqYn4v46O7UC1yPmaf2cjB_H-wmJKE-QwzAgO3ct-Vq5KanudcV5GMHfGH9Z5mg6j0wa44i9vdecdOB32qdMHcFu" frameborder="0" scrolling="auto" style="display:block;margin-left:auto;margin-right:auto;max-width:100%"></iframe>]]></content:encoded>
            <category>cloud</category>
            <category>devops</category>
        </item>
        <item>
            <title><![CDATA[Mastering REST API Debugging: A Guide with Wireshark]]></title>
            <link>https://entrecompetents.fr/blog/debug-api-wireshark</link>
            <guid>https://entrecompetents.fr/blog/debug-api-wireshark</guid>
            <pubDate>Mon, 09 Feb 2026 19:25:36 GMT</pubDate>
            <description><![CDATA[Debug any REST API using Wireshark. Troubleshoot an API when no debugging or logging tools are working.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="debug api avec wireshark" src="https://entrecompetents.fr/blog/assets/images/debug-api-wireshark-74633303bab67d5e242c649510f79aec.png" width="1200" height="480" class="img_ev3q"></p>
<p>When working with an API, it is important to be able to debug it.</p>
<p>API debugging tools can be divided into two classes: those that allow editing of the sent HTTP requests and those that inspect the network to see what has been sent retrospectively.</p>
<p>In the first category, there are well-known tools such as <a href="https://www.postman.com/" target="_blank" rel="noopener noreferrer">Postman</a>, <a href="https://insomnia.rest/" target="_blank" rel="noopener noreferrer">Insomnia</a> or <a href="https://curl.se/" target="_blank" rel="noopener noreferrer">Curl</a>.</p>
<table><thead><tr><th></th><th>Postman</th><th>Insomnia</th><th>Curl</th></tr></thead><tbody><tr><td>GUI</td><td>✅</td><td>✅</td><td>❌</td></tr><tr><td>Open source</td><td>❌</td><td>✅</td><td>✅</td></tr><tr><td>Export requests as code</td><td>✅</td><td>✅</td><td>❌</td></tr><tr><td>Import OpenAPI format</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>User friendly</td><td>⭐️⭐️⭐️⭐️</td><td>⭐️⭐️⭐️</td><td>⭐️</td></tr><tr><td>Installation process difficulty</td><td>⭐️⭐️</td><td>⭐️⭐️</td><td>⭐️⭐️⭐️⭐️</td></tr></tbody></table>
<p>In the second category, Wireshark overcomes most available solutions. Fortunately, it is open-source and free of charge.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="when-should-we-use-wireshark-">When should we use Wireshark ?<a href="https://entrecompetents.fr/blog/debug-api-wireshark#when-should-we-use-wireshark-" class="hash-link" aria-label="Direct link to When should we use Wireshark ?" title="Direct link to When should we use Wireshark ?">​</a></h2>
<p>Wireshark allows us to intercept network requests, making it useful when:</p>
<ul>
<li>We want to know the network requests of a tool without having access to its code.</li>
<li>We want to verify the content of requests sent by a service.</li>
</ul>
<p>Below, it is emphasized that Wireshark is non-intrusive in the sending or receiving of HTTP requests; it acts as an observer of the network.</p>
<p><img decoding="async" loading="lazy" alt="schéma de wireshark" src="https://entrecompetents.fr/blog/assets/images/wireshark_schema-0d1f2fd87777c7262ffbe1571437ced2.png" width="482" height="235" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="how-to-use-wireshark-">How to use Wireshark ?<a href="https://entrecompetents.fr/blog/debug-api-wireshark#how-to-use-wireshark-" class="hash-link" aria-label="Direct link to How to use Wireshark ?" title="Direct link to How to use Wireshark ?">​</a></h3>
<p>To install Wireshark, you can follow <a href="https://www.wireshark.org/download.html" target="_blank" rel="noopener noreferrer">the official installation page</a>.</p>
<p>Once installed and opened, Wireshark displays the available networks on the computer along with their activity (see screenshot below).</p>
<p><img decoding="async" loading="lazy" alt="wireshark screenshot interface" src="https://entrecompetents.fr/blog/assets/images/wireshark_screenshot_interface-2a125a0cd79c52d84dc5751e67576742.png" width="3104" height="1820" class="img_ev3q"></p>
<p>After selecting an interface, the user can start capturing requests. It's important to note that HTTPS requests will be captured but appear encrypted and unreadable.</p>
<p>As shown in the screenshot below, the user can also add filters to display only relevant requests.</p>
<p><img decoding="async" loading="lazy" alt="wireshark screenshot recording" src="https://entrecompetents.fr/blog/assets/images/wireshark_screenshot_recording-554c6a06b6175db378e7ad06c772d537.png" width="3104" height="1820" class="img_ev3q"></p>
<p>The request inspection interface is particularly useful. It allows a detailed look at the content of the HTTP request, helping to understand what the recipient has received.</p>
<p>Wireshark's "non-intrusive" capability makes it a valuable ally in debugging applications.</p>
<!-- -->
<iframe width="540" height="705" src="https://23a11111.sibforms.com/serve/MUIFAD4V-sjfjj3lAophYwHVb-SESnxq_HWg-plzTaW8rgGeG-yPYbFI8KeEg39tUVNQ0tVKOF2eoSlFHchztAiI2XzH7XA-UDWaIG5-vEuh7julQgBEdrhXjqYn4v46O7UC1yPmaf2cjB_H-wmJKE-QwzAgO3ct-Vq5KanudcV5GMHfGH9Z5mg6j0wa44i9vdecdOB32qdMHcFu" frameborder="0" scrolling="auto" style="display:block;margin-left:auto;margin-right:auto;max-width:100%"></iframe>]]></content:encoded>
            <category>dev</category>
            <category>cloud</category>
            <category>api</category>
            <category>network</category>
        </item>
        <item>
            <title><![CDATA[Mastering Network Debugging: A Guide with Tshark]]></title>
            <link>https://entrecompetents.fr/blog/debug-tshark-nogui</link>
            <guid>https://entrecompetents.fr/blog/debug-tshark-nogui</guid>
            <pubDate>Mon, 09 Feb 2026 19:25:36 GMT</pubDate>
            <description><![CDATA[Debugging the network when you only have access to a CLI (terminal)]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="debug api with tshark" src="https://entrecompetents.fr/blog/assets/images/debug-api-wireshark-74633303bab67d5e242c649510f79aec.png" width="1200" height="480" class="img_ev3q"></p>
<p>Numerous tools are available for network debugging, from debugging a REST API to analyzing traffic on a router or local network.</p>
<p>A comparison can be found on <a href="https://entrecompetents.fr/blog/debug-api-wireshark">our Wireshark article</a>.</p>
<p>Tshark is the CLI version of wireshark, and lets you capture with your favorite tool when you don't have access to a GUI, or when the CLI server simply doesn't have a graphical environment.</p>
<p>The advantage is that you can export the network capture and open it on your PC with Wireshark! We'll look at this at the end of the article.</p>
<h1>How to use Tshark</h1>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="installing-tshark">Installing tshark<a href="https://entrecompetents.fr/blog/debug-tshark-nogui#installing-tshark" class="hash-link" aria-label="Direct link to Installing tshark" title="Direct link to Installing tshark">​</a></h2>
<p>You can follow the <a href="https://tshark.dev/setup/install/" target="_blank" rel="noopener noreferrer">official documentation</a>, on debian-based just tap <code>apt install tshark</code>.</p>
<p>You can also get tshark by installing Wireshark. And like wireshark, tshark uses Dumpcap as its capture engine.</p>
<p>Tshark has the advantage of being a lightweight, powerful tool that can be installed on many platforms.</p>
<p>On installation, you can choose whether to give access to network analysis to all users or only to sudoers.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-do-i-capture-packages-with-tshark">How do I capture packages with Tshark?<a href="https://entrecompetents.fr/blog/debug-tshark-nogui#how-do-i-capture-packages-with-tshark" class="hash-link" aria-label="Direct link to How do I capture packages with Tshark?" title="Direct link to How do I capture packages with Tshark?">​</a></h2>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="useful-basic-commands">Useful basic commands<a href="https://entrecompetents.fr/blog/debug-tshark-nogui#useful-basic-commands" class="hash-link" aria-label="Direct link to Useful basic commands" title="Direct link to Useful basic commands">​</a></h3>
<p>To see the list of capturable interfaces: <code>tshark -D</code>. You also have the advantage of seeing virtual interfaces.</p>
<p>To capture: <code>tshark</code> or <code>tshark -i &lt;interface_name&gt;</code>.</p>
<p><img decoding="async" loading="lazy" alt="tshark interface capture" src="https://entrecompetents.fr/blog/assets/images/thsark-iface-692249ecc5715c9f353ce9e94bf7813b.png" width="1445" height="330" class="img_ev3q"></p>
<p>You can make complex filters with <code>tshark -f "${filter}</code> like <code>tcp port 80</code>, in the same way as filters on wireshark.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="further-information">Further information<a href="https://entrecompetents.fr/blog/debug-tshark-nogui#further-information" class="hash-link" aria-label="Direct link to Further information" title="Direct link to Further information">​</a></h3>
<p>The official documentation is very comprehensive, and the <code>tshark --help</code> command is very useful, as is the <em>man</em>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="export-and-open-capture-on-wireshark">Export and open capture on Wireshark<a href="https://entrecompetents.fr/blog/debug-tshark-nogui#export-and-open-capture-on-wireshark" class="hash-link" aria-label="Direct link to Export and open capture on Wireshark" title="Direct link to Export and open capture on Wireshark">​</a></h2>
<p>A huge advantage of tshark is its intercompatibility and capture format.</p>
<p>To save a capture in a file, use the <code>-w</code> option like this: <code>tshark -w &lt;filename&gt;.pcap</code>.</p>
<p>To read a pcap file, you have two options:</p>
<ul>
<li>open in GUI with Wireshark</li>
<li>open with tshark by coloring with: <code>tshark -r &lt;filename&gt;.pcap --color</code>.</li>
</ul>
<!-- -->
<iframe width="540" height="705" src="https://23a11111.sibforms.com/serve/MUIFAD4V-sjfjj3lAophYwHVb-SESnxq_HWg-plzTaW8rgGeG-yPYbFI8KeEg39tUVNQ0tVKOF2eoSlFHchztAiI2XzH7XA-UDWaIG5-vEuh7julQgBEdrhXjqYn4v46O7UC1yPmaf2cjB_H-wmJKE-QwzAgO3ct-Vq5KanudcV5GMHfGH9Z5mg6j0wa44i9vdecdOB32qdMHcFu" frameborder="0" scrolling="auto" style="display:block;margin-left:auto;margin-right:auto;max-width:100%"></iframe>]]></content:encoded>
            <category>dev</category>
            <category>cloud</category>
            <category>api</category>
        </item>
        <item>
            <title><![CDATA[Docker Commit or Dockerfile?]]></title>
            <link>https://entrecompetents.fr/blog/dockerCommitVsDockerFile</link>
            <guid>https://entrecompetents.fr/blog/dockerCommitVsDockerFile</guid>
            <pubDate>Mon, 09 Feb 2026 19:25:36 GMT</pubDate>
            <description><![CDATA[Explore the nuances between Docker Commit and Docker Build as we delve into their differences through a comprehensive analysis. Discover the intricacies of creating Docker images from existing containers and Dockerfiles, with a focus on size differentials and attribute variations. Uncover the ideal scenarios for utilizing Docker Commit to customize images for streamlined use and Docker Build for scripted, sustainable image creation. Join us in this insightful comparison to enhance your Docker proficiency.]]></description>
            <content:encoded><![CDATA[<p>Have you heard about the second command that allows you to create Docker images? 🐳</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">docker commit [container name] [image name]:[tag]</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This command creates an image by taking a kind of "snapshot" of the targeted container. However, I wondered what the differences were compared to docker build (aside from the Dockerfile).</p>
<p>I took the time to study a very simple case and examine the differences it produces:</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="differences-between-docker-commit-and-docker-build">Differences between Docker Commit and Docker Build:<a href="https://entrecompetents.fr/blog/dockerCommitVsDockerFile#differences-between-docker-commit-and-docker-build" class="hash-link" aria-label="Direct link to Differences between Docker Commit and Docker Build:" title="Direct link to Differences between Docker Commit and Docker Build:">​</a></h2>
<p>Both <code>docker commit</code> and <code>docker build</code> commands are used to create Docker images. However, <code>docker commit</code> creates an image from an existing container, while <code>docker build</code> creates an image from a Dockerfile.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="comparison-test-protocol-for-docker-commit-and-docker-build">Comparison Test Protocol for Docker Commit and Docker Build:<a href="https://entrecompetents.fr/blog/dockerCommitVsDockerFile#comparison-test-protocol-for-docker-commit-and-docker-build" class="hash-link" aria-label="Direct link to Comparison Test Protocol for Docker Commit and Docker Build:" title="Direct link to Comparison Test Protocol for Docker Commit and Docker Build:">​</a></h3>
<ol>
<li>
<p>Build an nginx:1.24.0 image with the addition of the iputils-ping package.</p>
</li>
<li>
<p>Make this addition in a single command.</p>
</li>
</ol>
<p><em>The Dockerfile :</em></p>
<div class="language-Dockerfile language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FROM nginx:1.24.0</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN apt update &amp;&amp; apt install iputils-ping -y</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>In this case, there is a difference of 1254 Bytes, approximately 0.0001 MB.</p>
<p>The exact sizes of the images are:</p>
<ul>
<li>155 182 582 bytes for docker build</li>
<li>155 183 836 bytes for docker commit</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="attribute-differences">Attribute Differences<a href="https://entrecompetents.fr/blog/dockerCommitVsDockerFile#attribute-differences" class="hash-link" aria-label="Direct link to Attribute Differences" title="Direct link to Attribute Differences">​</a></h3>
<p>While the image sizes have changed very little, the attributes have been radically modified:</p>
<p>The image created with docker build has lost the following attributes:</p>
<ul>
<li>Cmd</li>
<li>EntryPoint</li>
<li>Env</li>
<li>Labels</li>
<li>Exposed ports</li>
<li>Stop signal</li>
<li>Docker version</li>
<li>Parent</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion-on-the-differences-between-docker-commit-and-docker-build">Conclusion on the Differences between Docker Commit and Docker Build:<a href="https://entrecompetents.fr/blog/dockerCommitVsDockerFile#conclusion-on-the-differences-between-docker-commit-and-docker-build" class="hash-link" aria-label="Direct link to Conclusion on the Differences between Docker Commit and Docker Build:" title="Direct link to Conclusion on the Differences between Docker Commit and Docker Build:">​</a></h2>
<p>There is no better solution; they simply have different purposes:</p>
<p><strong>Docker commit</strong> should mainly be used to customize a Docker image to simplify its later use.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="example">Example:<a href="https://entrecompetents.fr/blog/dockerCommitVsDockerFile#example" class="hash-link" aria-label="Direct link to Example:" title="Direct link to Example:">​</a></h4>
<p>Adding a package, quickly modifying an image for testing purposes...</p>
<p><strong>Docker build</strong> should be used when creating a new use case, especially when wanting to keep a scripted version of the image in the form of a Dockerfile. This is much more sustainable than an image stored in a registry.</p>
<p>The sources of my experience and the entire process are available on this repository: <a href="https://github.com/LucasSovre/sourcesCommitVsBuild/tree/main" target="_blank" rel="noopener noreferrer">https://github.com/LucasSovre/sourcesCommitVsBuild/tree/main</a></p>
<p>This shouldn't affect the studied mechanics, but the tests were performed with an ARM (M1) processor.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="sources">Sources<a href="https://entrecompetents.fr/blog/dockerCommitVsDockerFile#sources" class="hash-link" aria-label="Direct link to Sources" title="Direct link to Sources">​</a></h3>
<ul>
<li><a href="https://docs.docker.com/engine/reference/commandline/commit/" target="_blank" rel="noopener noreferrer">Official Docker commit documentation</a></li>
<li><a href="https://docs.docker.com/engine/reference/commandline/build/" target="_blank" rel="noopener noreferrer">Official Docker build documentation</a></li>
<li><a href="https://docs.docker.com/engine/reference/builder/" target="_blank" rel="noopener noreferrer">Official Dockerfile documentation</a></li>
<li><a href="https://github.com/LucasSovre/sourcesCommitVsBuild/tree/main" target="_blank" rel="noopener noreferrer">Source Code</a></li>
</ul>
<!-- -->
<iframe width="540" height="705" src="https://23a11111.sibforms.com/serve/MUIFAD4V-sjfjj3lAophYwHVb-SESnxq_HWg-plzTaW8rgGeG-yPYbFI8KeEg39tUVNQ0tVKOF2eoSlFHchztAiI2XzH7XA-UDWaIG5-vEuh7julQgBEdrhXjqYn4v46O7UC1yPmaf2cjB_H-wmJKE-QwzAgO3ct-Vq5KanudcV5GMHfGH9Z5mg6j0wa44i9vdecdOB32qdMHcFu" frameborder="0" scrolling="auto" style="display:block;margin-left:auto;margin-right:auto;max-width:100%"></iframe>]]></content:encoded>
            <category>docker</category>
            <category>devops</category>
            <category>cloud</category>
        </item>
        <item>
            <title><![CDATA[Listmonk, the alternative to Mailchimp]]></title>
            <link>https://entrecompetents.fr/blog/listmonk</link>
            <guid>https://entrecompetents.fr/blog/listmonk</guid>
            <pubDate>Mon, 09 Feb 2026 19:25:36 GMT</pubDate>
            <description><![CDATA[Explore Listmonk, a free alternative to Mailchimp for your email campaigns. Compare features, delve into financial benefits, and learn how to seamlessly integrate Listmonk into your infrastructure. Choose an efficient and cost-effective solution for your email marketing needs.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="open sources tools lists" src="https://entrecompetents.fr/blog/assets/images/MailChimpVsListmonk-866b2ce89793a446262108502b33a465.jpg" width="1200" height="480" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-is-the-point-of-theses-tools-">What is the point of theses tools ?<a href="https://entrecompetents.fr/blog/listmonk#what-is-the-point-of-theses-tools-" class="hash-link" aria-label="Direct link to What is the point of theses tools ?" title="Direct link to What is the point of theses tools ?">​</a></h2>
<p>Discover two tools designed for collecting email lists and sending bulk emails. Explore their features, benefits, and find the right solution for your mass email needs.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="where-to-find-them-">Where to find them ?<a href="https://entrecompetents.fr/blog/listmonk#where-to-find-them-" class="hash-link" aria-label="Direct link to Where to find them ?" title="Direct link to Where to find them ?">​</a></h2>
<p><a href="https://listmonk.app/" target="_blank" rel="noopener noreferrer">official Listmonk website</a></p>
<p><a href="https://mailchimp.com/" target="_blank" rel="noopener noreferrer">official Mailchimp website</a></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="mailchimp-an-all-in-one-tool">MailChimp an "all-in-one" tool<a href="https://entrecompetents.fr/blog/listmonk#mailchimp-an-all-in-one-tool" class="hash-link" aria-label="Direct link to MailChimp an &quot;all-in-one&quot; tool" title="Direct link to MailChimp an &quot;all-in-one&quot; tool">​</a></h3>
<p>MailChimp is an "all-in-one" tool. It includes mail servers, automation, data collection, registration forms... Almost nothing is missing.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="listmonk-a-tool-focused-on-emails">Listmonk, a tool focused on emails.<a href="https://entrecompetents.fr/blog/listmonk#listmonk-a-tool-focused-on-emails" class="hash-link" aria-label="Direct link to Listmonk, a tool focused on emails." title="Direct link to Listmonk, a tool focused on emails.">​</a></h3>
<p>Listmonk doesn't burden itself with all the features of automation or a WYSIWYG editor (which would still be nice). But what it does, it does well, and most importantly, it uses external mail servers.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="on-the-financial-side">On the financial side:<a href="https://entrecompetents.fr/blog/listmonk#on-the-financial-side" class="hash-link" aria-label="Direct link to On the financial side:" title="Direct link to On the financial side:">​</a></h3>
<p>In terms of pricing, Listmonk is unbeatable since it is free. Even when factoring in the costs of servers and mail server providers, Mailchimp's prices remain much higher.</p>
<p>With a user base of 10,000 users and an average of 5 emails per person per month:</p>
<p>MailChimp, as of December 3, 2023, costs €125/month.</p>
<p>Assuming you use Amazon SES for emails (€0.09/1k emails), MailChimp would cost you €4.5 per month.</p>
<p>Your infrastructure costs (solely dedicated to Listmonk) would need to exceed €120 per month for MailChimp to be more cost-effective.</p>
<p>It's evident that in terms of savings, Listmonk can be highly beneficial, especially considering that with Listmonk, your subscriber lists are unlimited.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="how-to-address-listmonks-feature-gaps">How to Address Listmonk's Feature Gaps:<a href="https://entrecompetents.fr/blog/listmonk#how-to-address-listmonks-feature-gaps" class="hash-link" aria-label="Direct link to How to Address Listmonk's Feature Gaps:" title="Direct link to How to Address Listmonk's Feature Gaps:">​</a></h3>
<p>As mentioned earlier, Listmonk lacks user journey automation or automatic email sending capabilities. However, with a self-hosted n8n, you can easily create more advanced automations without the need for coding.</p>
<p>Below is an example of an n8n-listmonk automation workflow. This workflow is triggered by a webhook call, retrieves user information from the database, creates the user in the listmonk database if necessary, and then sends them an email.</p>
<p><img decoding="async" loading="lazy" alt="exemple de workflow n8n" src="https://entrecompetents.fr/blog/assets/images/n8n-listmonk-6b269daf7ee440de646fe1fdc99bcbc2.png" width="2122" height="828" class="img_ev3q"></p>
<p>In the following example, this workflow allows scheduling the sending of a transactional email to a user in n days|hours|minutes.</p>
<p><img decoding="async" loading="lazy" alt="exemple de workflow n8n" src="https://entrecompetents.fr/blog/assets/images/n8n-listmonk-wait-50e509d937d04eefdee141b9a1d960bf.png" width="1358" height="576" class="img_ev3q"></p>
<p>While Listmonk may lack the WYSIWYG editor found in MailChimp, a few email templates are likely to suffice. You can easily acquire them for a few euros, compensating for this specific feature gap.</p>
<p>You can find the n8n listmonk node in <a href="https://www.npmjs.com/package/n8n-nodes-listmonk" target="_blank" rel="noopener noreferrer">this npm package</a></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-to-install-listmonk-">How to install Listmonk ?<a href="https://entrecompetents.fr/blog/listmonk#how-to-install-listmonk-" class="hash-link" aria-label="Direct link to How to install Listmonk ?" title="Direct link to How to install Listmonk ?">​</a></h2>
<p><a href="https://listmonk.app/docs/installation/" target="_blank" rel="noopener noreferrer">The Listmonk installation page</a> is comprehensive, but here's a quick summary:</p>
<p>If you don't already have a PostgreSQL database, you can use the following command:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">mkdir listmonk &amp;&amp; cd listmonk</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">sh -c "$(curl -fsSL https://raw.githubusercontent.com/knadh/listmonk/master/install-prod.sh)"</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>If you already have a database, just launch a container with the associated configuration.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-to-integrate-listmonk-into-your-infrastructure">How to Integrate Listmonk into Your Infrastructure:<a href="https://entrecompetents.fr/blog/listmonk#how-to-integrate-listmonk-into-your-infrastructure" class="hash-link" aria-label="Direct link to How to Integrate Listmonk into Your Infrastructure:" title="Direct link to How to Integrate Listmonk into Your Infrastructure:">​</a></h2>
<p>Here's a diagram that consolidates all the previous elements to provide an example of how to integrate Listmonk into your tools.</p>
<p><img decoding="async" loading="lazy" alt="Exemple d&amp;#39;architecture" src="https://entrecompetents.fr/blog/assets/images/listmonk-492368af54315c5a2f84479036d0c309.jpg" width="625" height="727" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="sources">Sources<a href="https://entrecompetents.fr/blog/listmonk#sources" class="hash-link" aria-label="Direct link to Sources" title="Direct link to Sources">​</a></h2>
<ul>
<li><a href="https://mailchimp.com/" target="_blank" rel="noopener noreferrer">https://mailchimp.com</a></li>
<li><a href="https://listmonk.app/" target="_blank" rel="noopener noreferrer">https://listmonk.app</a></li>
<li><a href="https://listmonk.app/docs/" target="_blank" rel="noopener noreferrer">https://listmonk.app/docs/</a></li>
<li><a href="https://aws.amazon.com/fr/ses/" target="_blank" rel="noopener noreferrer">https://aws.amazon.com/fr/ses/</a></li>
<li><a href="https://n8n.io/" target="_blank" rel="noopener noreferrer">https://n8n.io</a></li>
</ul>
<!-- -->
<iframe width="540" height="705" src="https://23a11111.sibforms.com/serve/MUIFAD4V-sjfjj3lAophYwHVb-SESnxq_HWg-plzTaW8rgGeG-yPYbFI8KeEg39tUVNQ0tVKOF2eoSlFHchztAiI2XzH7XA-UDWaIG5-vEuh7julQgBEdrhXjqYn4v46O7UC1yPmaf2cjB_H-wmJKE-QwzAgO3ct-Vq5KanudcV5GMHfGH9Z5mg6j0wa44i9vdecdOB32qdMHcFu" frameborder="0" scrolling="auto" style="display:block;margin-left:auto;margin-right:auto;max-width:100%"></iframe>]]></content:encoded>
            <category>self-hosted</category>
        </item>
        <item>
            <title><![CDATA[All possibles types in a SurrealDB Schemafull table]]></title>
            <link>https://entrecompetents.fr/blog/surrealdb-schemafull-types</link>
            <guid>https://entrecompetents.fr/blog/surrealdb-schemafull-types</guid>
            <pubDate>Mon, 09 Feb 2026 19:25:36 GMT</pubDate>
            <description><![CDATA[This article lists all the types available in SurrealDB schemafull table and how to use them.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="surreal db schemafull guide" src="https://entrecompetents.fr/blog/assets/images/surrealdb-types-6ba11cb2c958bfad130a5c56cb566af8.png" width="1200" height="480" class="img_ev3q"></p>
<p>SurrealDB is a powerfull database, which 1.0 has been released in 2023. It's a new gen database, which can be schemafull or schemaless. In this article, we will focus on the schemafull part of SurrealDB.</p>
<p>Even if the official SurrealDB documentation is quite complete about SurrealQL language querrying, it lacks a bit of information about the types available in a schemafull table. This article aims to fill this gap.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="reminder-about-creating-a-table-and-field">Reminder about creating a table and field<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#reminder-about-creating-a-table-and-field" class="hash-link" aria-label="Direct link to Reminder about creating a table and field" title="Direct link to Reminder about creating a table and field">​</a></h2>
<p>To create a table in SurrealDB, you need to have previously defined a database and namespace. Then you can create a table with the following command:</p>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE TABLE product SCHEMAFULL;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then to define a field in a table, you can use the following command:</p>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD my_field ON TABLE product TYPE data_type;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h1>Types available in SurrealDB schemafull table</h1>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="basic-types">basic types<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#basic-types" class="hash-link" aria-label="Direct link to basic types" title="Direct link to basic types">​</a></h2>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="any">Any<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#any" class="hash-link" aria-label="Direct link to Any" title="Direct link to Any">​</a></h3>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD my_field ON TABLE product TYPE any;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="booleans">Booleans<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#booleans" class="hash-link" aria-label="Direct link to Booleans" title="Direct link to Booleans">​</a></h3>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD active ON TABLE product TYPE bool;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="numbers">Numbers<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#numbers" class="hash-link" aria-label="Direct link to Numbers" title="Direct link to Numbers">​</a></h3>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="any-number">Any number<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#any-number" class="hash-link" aria-label="Direct link to Any number" title="Direct link to Any number">​</a></h4>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD price ON TABLE product TYPE number;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="integer">Integer<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#integer" class="hash-link" aria-label="Direct link to Integer" title="Direct link to Integer">​</a></h4>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD price ON TABLE product TYPE int;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="float">Float<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#float" class="hash-link" aria-label="Direct link to Float" title="Direct link to Float">​</a></h4>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD price ON TABLE product TYPE float;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="decimal">Decimal<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#decimal" class="hash-link" aria-label="Direct link to Decimal" title="Direct link to Decimal">​</a></h4>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD price ON TABLE product TYPE decimal;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="null">Null<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#null" class="hash-link" aria-label="Direct link to Null" title="Direct link to Null">​</a></h3>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD price ON TABLE product TYPE null;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="string">String<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#string" class="hash-link" aria-label="Direct link to String" title="Direct link to String">​</a></h3>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD name ON TABLE product TYPE string;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="bytes">Bytes<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#bytes" class="hash-link" aria-label="Direct link to Bytes" title="Direct link to Bytes">​</a></h3>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD icon ON TABLE product TYPE bytes;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="uuid">Uuid<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#uuid" class="hash-link" aria-label="Direct link to Uuid" title="Direct link to Uuid">​</a></h3>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD ext_id ON TABLE product TYPE uuid;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="times-related-types">Times related types<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#times-related-types" class="hash-link" aria-label="Direct link to Times related types" title="Direct link to Times related types">​</a></h3>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="datetime">Datetime<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#datetime" class="hash-link" aria-label="Direct link to Datetime" title="Direct link to Datetime">​</a></h4>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD created_at ON TABLE product TYPE datetime;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="duration">Duration<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#duration" class="hash-link" aria-label="Direct link to Duration" title="Direct link to Duration">​</a></h4>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD duration ON TABLE product TYPE duration;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="point">Point<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#point" class="hash-link" aria-label="Direct link to Point" title="Direct link to Point">​</a></h3>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD location ON TABLE product TYPE point;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="record">Record<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#record" class="hash-link" aria-label="Direct link to Record" title="Direct link to Record">​</a></h3>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD created_by ON TABLE product TYPE record;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="composed-types">Composed types<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#composed-types" class="hash-link" aria-label="Direct link to Composed types" title="Direct link to Composed types">​</a></h2>
<p>Composed types are formed by combining basic types with <code>composed_type&lt;type&gt;</code>.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="optional-type">Optional type<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#optional-type" class="hash-link" aria-label="Direct link to Optional type" title="Direct link to Optional type">​</a></h3>
<p>By default, all fields are required. If you want to make a field optional, you can use the <code>option</code> type.</p>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD description ON TABLE product TYPE option&lt;string&gt;;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="multiple-types">Multiple types<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#multiple-types" class="hash-link" aria-label="Direct link to Multiple types" title="Direct link to Multiple types">​</a></h3>
<p>If you want to allow multiple types for a field, you can use the <code>either</code> type.</p>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD additional_propertie ON TABLE product TYPE string|number;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="array">Array<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#array" class="hash-link" aria-label="Direct link to Array" title="Direct link to Array">​</a></h3>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD tags ON TABLE product TYPE array&lt;string&gt;;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="set">Set<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#set" class="hash-link" aria-label="Direct link to Set" title="Direct link to Set">​</a></h3>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD tags ON TABLE product TYPE set&lt;string&gt;;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="geometry">Geometry<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#geometry" class="hash-link" aria-label="Direct link to Geometry" title="Direct link to Geometry">​</a></h3>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFINE FIELD location ON TABLE product TYPE geometry&lt;line&gt;;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>In this case the only types allowed are the following defined in the <a href="https://surrealdb.com/docs/surrealdb/surrealql/datamodel/geometries" target="_blank" rel="noopener noreferrer">SurrealDB documentation</a>
Theses types respects the <a href="https://datatracker.ietf.org/doc/html/rfc7946" target="_blank" rel="noopener noreferrer">GeoJSON format (defined in rfc7946)</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="surrealdb-full-course">SurrealDB full course<a href="https://entrecompetents.fr/blog/surrealdb-schemafull-types#surrealdb-full-course" class="hash-link" aria-label="Direct link to SurrealDB full course" title="Direct link to SurrealDB full course">​</a></h2>
<p>If you want to learn more about SurrealDB, you can subscribe to our newsletter to be informed about the course we are preparing, and get a discount when it will be released.</p>
<!-- -->
<iframe width="540" height="705" src="https://23a11111.sibforms.com/serve/MUIFAD4V-sjfjj3lAophYwHVb-SESnxq_HWg-plzTaW8rgGeG-yPYbFI8KeEg39tUVNQ0tVKOF2eoSlFHchztAiI2XzH7XA-UDWaIG5-vEuh7julQgBEdrhXjqYn4v46O7UC1yPmaf2cjB_H-wmJKE-QwzAgO3ct-Vq5KanudcV5GMHfGH9Z5mg6j0wa44i9vdecdOB32qdMHcFu" frameborder="0" scrolling="auto" style="display:block;margin-left:auto;margin-right:auto;max-width:100%"></iframe>]]></content:encoded>
            <category>cloud</category>
            <category>dev</category>
            <category>database</category>
        </item>
        <item>
            <title><![CDATA[How to create a nextjs + mongoDB SaaS fully self-hosted]]></title>
            <link>https://entrecompetents.fr/blog/theStackBehindComposeCraft</link>
            <guid>https://entrecompetents.fr/blog/theStackBehindComposeCraft</guid>
            <pubDate>Mon, 09 Feb 2026 19:25:36 GMT</pubDate>
            <description><![CDATA[How I have created a SaaS with nextJS + mongoDB, that's fully self-hosted using docker and coolify.]]></description>
            <content:encoded><![CDATA[<p>I wanted to build a SaaS that allow users to view, edit and share docker-compose files easily. If you don't know anything about docker, that's totally okay. This article is focused about the stack behind the SaaS.</p>
<p>So, my needs where :</p>
<ul>
<li>A user system</li>
<li>Somes analytics</li>
<li>A blog sytem</li>
<li>A documentation system</li>
<li>A database that allow me to quickly try new things</li>
<li>The stack has to be self-hostable easily</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="choose-a-database">Choose a database<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#choose-a-database" class="hash-link" aria-label="Direct link to Choose a database" title="Direct link to Choose a database">​</a></h2>
<p>The database, is a though choice. First should i go SQL or No-SQL ?</p>
<p>In my opinion, we should choose a database that fit our data shape, and not shape our data to fit in the database.
And if you know a little of the docker-composes, that's a very complex data shape to store, as is it quite inconsistent (I mean, even if this is based on yaml, there is so much differents possibilities). To store it in an sql database would be a nightmare, the only "easy way" would to just considere it as a raw text and store it in a text cell (or byte but anyway).</p>
<p>Yes i could use the postgresql JSON cell to create a way to store it convientently, but remember our need : <code>keep it simple</code>.</p>
<p>So i choosed No-SQL for the ability to store non structured data.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="but-what-no-sql-database-">But what No-SQL database ?<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#but-what-no-sql-database-" class="hash-link" aria-label="Direct link to But what No-SQL database ?" title="Direct link to But what No-SQL database ?">​</a></h3>
<p>Choosing a No-SQL database is just half the way to choose a database. There is still so much questions to pick one.
But always remember this when you are a small team or solo dev : <code>keep it simple</code>.</p>
<p>So i will restrict my questions to :</p>
<ul>
<li>Is there an easy to use TypeScript Driver ?</li>
<li>Is the database secure ?</li>
<li>Can i self-host it ?</li>
<li>Is there a big community ?</li>
</ul>
<p>I thought about surealDB and mongoDB. They both have a TS sdk, they dont' have new CVE each morning, and they are fully self-hostable.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="surrealdb">SurrealDB<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#surrealdb" class="hash-link" aria-label="Direct link to SurrealDB" title="Direct link to SurrealDB">​</a></h4>
<p>SurrealDB could be a really good way to go, has it can be used as an sql db for my user part and noSQL for my data-processing and job part.</p>
<p>But the big drawback is the lack of maturity. Even if the devs are quite fast on issue peoples submits, their is not so much stack-overflow or any knowledge place about it.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="the-final-choice--mongodb">The final choice : MongoDB<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#the-final-choice--mongodb" class="hash-link" aria-label="Direct link to The final choice : MongoDB" title="Direct link to The final choice : MongoDB">​</a></h4>
<p>So, at the end i choosed mongoDB as it would allow me to quickly change the dataModel of my entities, and there is a much stronger documentation knowledge about it.</p>
<p>At this point i knew i will use nextJS + mongoDB, but what about the bloging system, the documentation, and the analytics ?</p>
<p><img decoding="async" loading="lazy" alt="illustration" src="https://entrecompetents.fr/blog/assets/images/archi-composecraft-1-191983d846af38f73bba71888598d068.png" width="653" height="242" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-self-hosting-part">The self hosting part<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#the-self-hosting-part" class="hash-link" aria-label="Direct link to The self hosting part" title="Direct link to The self hosting part">​</a></h2>
<p>Before going further i started my first beta with very few user. So i needed to self-host the stack.
My two biggest concern where : I want it cheap, and i want it easy.</p>
<p>I choosed hetzner.com has they are really cheap and reliable. 4.51 € /month for 2 Cpu, 4Gb Ram 20Tb bandwith and 40gb SSD.
To manage my VPS i choosed <a href="https://coolify.io/" target="_blank" rel="noopener noreferrer">coolify</a>, has it's fully open-source and allow me to easily deploy new services.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-analytics-blog-and-documentation-">The analytics, blog and documentation :<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#the-analytics-blog-and-documentation-" class="hash-link" aria-label="Direct link to The analytics, blog and documentation :" title="Direct link to The analytics, blog and documentation :">​</a></h2>
<p>The analytics choice was really straigth forward for me as i use <a href="https://umami.is/" target="_blank" rel="noopener noreferrer">Umami analytics</a> from 2020. It's fully self-hosted, open source and GDPR complient.</p>
<p>My blog need where a little bit weird as i needed something headless but with an admin UI and self-hostable.
I could go for Strapi, but i ended with Directus for no particular reason both could perfectly fit in my use case.</p>
<p>About the documentation, my choice has been pushed by : <code>cheap and fast</code>. So i could go with any Mardkown based documentation. Here is somes good choices bellow :</p>
<ul>
<li>Docusaurus</li>
<li>Hugo</li>
<li>mkDocs</li>
<li>markdoc</li>
</ul>
<p>I ended with Docusaurus because it's based on react, and in the future, if there is any need i could easily add custom components.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="isnt-there-any-seo-issues-using-a-different-techno-for-documentation-and-bloging-">Is'nt there any SEO issues using a different techno for documentation and bloging ?<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#isnt-there-any-seo-issues-using-a-different-techno-for-documentation-and-bloging-" class="hash-link" aria-label="Direct link to Is'nt there any SEO issues using a different techno for documentation and bloging ?" title="Direct link to Is'nt there any SEO issues using a different techno for documentation and bloging ?">​</a></h4>
<p>To avoid SEO issue that are associated with multi-domain website such as could be <code>composecraft.com</code>, <code>docs.composecraft.com</code>, <code>blog.composecraft.com</code>.</p>
<p>I Opted for a different way to go :</p>
<p>First my blog system is more a library system. Directus only help me to manage the entries, my data contains a markdown field and directly feed my nextJS, so for the blog part (or library as you want) everything is under composecraft.com/library and nextJS handle it with the next-remote-mdx library.</p>
<p>But although i could used the same patern for my documentation, i choosed docusaurus. Why ?
Because my documentation will quickly evolve, and i don't want to mess with any nextjs question when writting documentation. I just want to writte mardown file and do not care about mobile responsive, about performance or anything.
Using Docusaurus automaticly enforce my documentation to be structured, easy to edit and easy to use by my users.</p>
<p>So i just setted my reverse-proxy to send any request under /doc to the Docusaurus docker that running managed by my coolify.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-reverse-proxy-">The reverse proxy :<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#the-reverse-proxy-" class="hash-link" aria-label="Direct link to The reverse proxy :" title="Direct link to The reverse proxy :">​</a></h2>
<p>In my opinion, especialy for choosing a reverse-proxy for low/medium load website go with the one you love. There is no "compute" better choice between ngninx, trafeik, apache or caddy as you stay with a simple server and no load balancing or complex routing.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-backups-">The backups ?<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#the-backups-" class="hash-link" aria-label="Direct link to The backups ?" title="Direct link to The backups ?">​</a></h2>
<p>How does i ensure my self-hosted database won't desapear when my server will burn because of my to many users ?</p>
<p>Until now my server is far away from burning from my to many user, but anyway i have integrated a backup mechnism. Which one ? Every day at 3 am a complete database backup is schedule and stored in a distant S3 on a cloud provider (a different from my VPS one to prevent from them putting the S3 bucket in the same datacenter as my production server).</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="last-concern--the-cdn">Last concern : the CDN<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#last-concern--the-cdn" class="hash-link" aria-label="Direct link to Last concern : the CDN" title="Direct link to Last concern : the CDN">​</a></h3>
<p>As a small product that is used worldwide i faced a problem : my US users complains about latency, and indeed the server is located in Europe.</p>
<p>Easy answer : i could have a US instance !</p>
<p>But ... remember it's a small projet and i want it <code>cheap and easy</code>. So i choosed to call cloudflare to help me with their free CDN. Yes it's not a fully self hosted privacy powered stack. But as a small product team it's the best cost/performance chose i cloud make.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-final-scheme-">The final scheme :<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#the-final-scheme-" class="hash-link" aria-label="Direct link to The final scheme :" title="Direct link to The final scheme :">​</a></h2>
<p><img decoding="async" loading="lazy" alt="final archi" src="https://entrecompetents.fr/blog/assets/images/archi-composecraft-2-2a7b89cda2f3f07b5953ded5090644ab.png" width="1471" height="451" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="somes-thoughts">Somes thoughts<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#somes-thoughts" class="hash-link" aria-label="Direct link to Somes thoughts" title="Direct link to Somes thoughts">​</a></h2>
<p>Could i builded it simpler ?</p>
<p>Yes i definitly clould : If my final client was someone non tech at all i would have worked more on the docs and reused directly directus to manage my docs in the same time as my libraries. And if the client do not want to infere at all with linux server : using mongoDb atlas and cloud solution for analytics, and something like vercel, netifly or fly.io to deploy my stateless nextjs app.</p>
<p>But as I am a linux lover and i am really not afraid of getting my hand on code, i thinks this is the most cost efficient and easy maintainable stack that fit me.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="wana-go-deeper-">Wana go deeper ?<a href="https://entrecompetents.fr/blog/theStackBehindComposeCraft#wana-go-deeper-" class="hash-link" aria-label="Direct link to Wana go deeper ?" title="Direct link to Wana go deeper ?">​</a></h2>
<p>I will soon create article about the CI/CD pipeline, and another one about the internal nextjs logic. keep updated about new blogs posts subscribing to the newsletter bellow :</p>
<!-- -->
<iframe width="540" height="705" src="https://23a11111.sibforms.com/serve/MUIFAD4V-sjfjj3lAophYwHVb-SESnxq_HWg-plzTaW8rgGeG-yPYbFI8KeEg39tUVNQ0tVKOF2eoSlFHchztAiI2XzH7XA-UDWaIG5-vEuh7julQgBEdrhXjqYn4v46O7UC1yPmaf2cjB_H-wmJKE-QwzAgO3ct-Vq5KanudcV5GMHfGH9Z5mg6j0wa44i9vdecdOB32qdMHcFu" frameborder="0" scrolling="auto" style="display:block;margin-left:auto;margin-right:auto;max-width:100%"></iframe>]]></content:encoded>
            <category>compose craft</category>
            <category>self-hosted</category>
            <category>dev</category>
        </item>
    </channel>
</rss>