<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>writefreely &amp;mdash; Blog - Kais BETTAIEB</title>
    <link>https://blog.kaisbettaieb.dev/tag:writefreely</link>
    <description>Python Developer crafting robust applications at the intersection of design, technology, and user experience.</description>
    <pubDate>Tue, 28 Apr 2026 11:13:57 +0000</pubDate>
    <item>
      <title>Adding Comments to WriteFreely with Cusdis</title>
      <link>https://blog.kaisbettaieb.dev/adding-comments-to-writefreely-with-cusdis</link>
      <description>&lt;![CDATA[#cusdis #comments #writefreely #javascript &#xA;&#xA;WriteFreely is beloved for its minimalism. However, fostering a community often requires a feedback loop. The challenge is adding comments without shattering that clean, distraction-free aesthetic.&#xA;!--more--&#xA;I recently integrated Cusdis—a lightweight, open-source, and privacy-friendly comment system—into this blog. It aligns seamlessly with WriteFreely’s ethos: it requires no user tracking and adapts beautifully with a bit of custom CSS.&#xA;&#xA;Here is how to implement a fully dynamic, styled comment section. This script ensures the widget loads only on individual posts (keeping your homepage pristine) and automatically respects dark mode.&#xA;&#xA;Step 1: Get Your Cusdis ID&#xA;&#xA;First, you need a running Cusdis instance. You have two paths:&#xA;&#xA;Cloud: Create a free account at Cusdis.com.&#xA;Self-Hosted: Host it yourself on a VPS (using Coolify or Docker) for 100% data ownership.&#xA;&#xA;Once you have access to your dashboard, copy your App ID (e.g., f42fd160-a4f2-...). You will need this for the JavaScript configuration below.&#xA;&#xA;Step 2: The Logic (Why Custom JS?)&#xA;&#xA;Since WriteFreely lacks native support for editing individual post templates, we cannot simply paste the standard Cusdis embed code. A static embed cannot automatically fetch the unique Page ID or Title for every article.&#xA;&#xA;Instead, we use Custom JavaScript to:&#xA;&#xA;Verify context: Check if the user is viewing a single post (ignoring the homepage).&#xA;Build the UI: Dynamically append a &#34;Comments&#34; container to the bottom of the article.&#xA;Inject the Widget: Initialize Cusdis with the correct metadata (ID, URL, Title).&#xA;Style: Force the widget into &#34;Dark Mode&#34; to match the theme.&#xA;&#xA;Step 3: The JavaScript Code&#xA;&#xA;Navigate to your WriteFreely dashboard, go to Customize   Custom JavaScript, and paste the code below.&#xA;&#xA;  Note: Be sure to replace &#39;YOUR-APP-ID-HERE&#39; with the actual ID you copied in Step 1.&#xA;&#xA;(function() {&#xA;    // Only run this script if we are on a post page (body#post)&#xA;    // This prevents the comments from loading on the homepage or collections.&#xA;    var postBody = document.querySelector(&#39;body#post&#39;);&#xA;    &#xA;    if (postBody) {&#xA;        // 1. Create the container div for Cusdis&#xA;        var cusdisContainer = document.createElement(&#39;div&#39;);&#xA;        cusdisContainer.id = &#39;cusdisthread&#39;;&#xA;        cusdisContainer.dataset.host = &#39;https://cusdis.com&#39;;&#xA;        cusdisContainer.dataset.appId = &#39;YOUR-APP-ID-HERE&#39;; // PASTE YOUR ID HERE&#xA;        cusdisContainer.dataset.pageId = window.location.pathname;&#xA;        cusdisContainer.dataset.pageUrl = window.location.href;&#xA;        cusdisContainer.dataset.pageTitle = document.title;&#xA;        cusdisContainer.dataset.theme = &#39;dark&#39;; // Forces dark mode&#xA;&#xA;        // 2. Append the container to the article or main content area&#xA;        // Note: &#39;article&#39; is standard in most WriteFreely themes. &#xA;        var article = document.querySelector(&#39;article&#39;);&#xA;        if (article) {&#xA;            article.appendChild(cusdisContainer);&#xA;        }&#xA;&#xA;        // 3. Load the Cusdis SDK asynchronously&#xA;        var script = document.createElement(&#39;script&#39;);&#xA;        script.src = &#39;https://cusdis.com/js/cusdis.es.js&#39;;&#xA;        script.async = true;&#xA;        script.defer = true;&#xA;        document.body.appendChild(script);&#xA;    }&#xA;})();&#xA;&#xA;Step 4: The styling&#xA;&#xA;Now that the functionality is in place, let&#39;s style the container to look like a piece of retro-futuristic hardware. We want a monospace font, a &#34;console&#34; border, and a distinct separation from the rest of your content.&#xA;&#xA;Navigate to Customize   Custom CSS and add the following:&#xA;&#xA;/ --- VISIBILITY RULES of COMMENT SECTION --- /&#xA;body#collection #comment-section,&#xA;body#subpage #comment-section {&#xA;    display: none !important;&#xA;}&#xA;&#xA;body#post #comment-section {&#xA;    display: block !important;&#xA;}&#xA;&#xA;/ --- END VISIBILITY RULES OF COMMENT SECTION -- /&#xA;/ --- CUSDIS COMMENT SECTION FIXES --- /&#xA;comment-section {&#xA;    margin-top: 3rem; / space from article content /&#xA;    width: 100%;&#xA;    max-width: 100%;&#xA;}&#xA;&#xA;/ Cusdis iframe container /&#xA;cusdisthread {&#xA;    width: 100% !important;&#xA;    min-height: 500px; / initial height /&#xA;    border-radius: 8px;&#xA;    overflow: hidden; / remove inner scrollbars /&#xA;    background-color: var(--surface-color) !important;&#xA;    border: 1px solid var(--border-color) !important;&#xA;    box-shadow: 0 0 15px rgba(0, 0, 0, 0.4);&#xA;}&#xA;&#xA;/ Make the iframe responsive /&#xA;cusdisthread iframe {&#xA;    width: 100% !important;&#xA;    min-height: 500px !important;&#xA;    height: auto !important;&#xA;    border: none !important;&#xA;}&#xA;&#xA;/ Dark theme for Cusdis content /&#xA;cusdisthread {&#xA;    --cusdis-background: var(--surface-color);&#xA;    --cusdis-text-color: var(--text-color);&#xA;    --cusdis-accent-color: var(--accent-color);&#xA;    --cusdis-border-color: var(--border-color);&#xA;    --cusdis-input-background: #222; / input background /&#xA;    --cusdis-input-text-color: #eee; / input text /&#xA;    --cusdis-button-background: var(--accent-color);&#xA;    --cusdis-button-text-color: #000;&#xA;}&#xA;&#xA;/ Force Cusdis to expand to fit content (if Cusdis JS supports it) /&#xA;cusdisthread iframe {&#xA;    height: 100% !important;&#xA;}&#xA;&#xA;/ Inputs and textareas /&#xA;cusdisthread textarea,&#xA;cusdisthread input {&#xA;    background-color: #222 !important;&#xA;    color: #eee !important;&#xA;    border: 1px solid var(--border-color) !important;&#xA;    border-radius: 6px;&#xA;    padding: 8px;&#xA;}&#xA;&#xA;/ Submit button /&#xA;cusdisthread button {&#xA;    background-color: var(--accent-color) !important;&#xA;    color: #000 !important;&#xA;    font-weight: 700;&#xA;    border-radius: 6px !important;&#xA;    padding: 6px 14px !important;&#xA;    border: none !important;&#xA;    cursor: pointer;&#xA;    transition: all 0.2s ease;&#xA;}&#xA;&#xA;cusdisthread button:hover {&#xA;    background-color: #3aa8d1 !important;&#xA;    box-shadow: 0 0 12px rgba(98, 200, 243, 0.5) !important;&#xA;    color: #000 !important;&#xA;}&#xA;&#xA;/ Scrollbar styling for iframe if it still scrolls /&#xA;cusdisthread iframe::-webkit-scrollbar {&#xA;    width: 8px;&#xA;}&#xA;&#xA;cusdisthread iframe::-webkit-scrollbar-thumb {&#xA;    background-color: #555;&#xA;    border-radius: 4px;&#xA;}&#xA;&#xA;cusdisthread iframe::-webkit-scrollbar-track {&#xA;    background-color: #111;&#xA;}&#xA;&#xA;/ Optional: smooth font for Cusdis /&#xA;cusdisthread,&#xA;cusdisthread iframe {&#xA;    font-family: &#39;Nunito&#39;, sans-serif !important;&#xA;}&#xA;&#xA;Final Thoughts&#xA;&#xA;And that is it. You now have a lightweight, privacy-respecting comment section that loads exclusively on your articles.&#xA;&#xA;By injecting the widget via JavaScript, you bypass WriteFreely&#39;s template limitations while maintaining total control over the look and feel. Your readers can now interact with your content in a space that feels like a natural extension of your blog&#39;s &#34;hacker&#34; aesthetic—no tracking pixels attached.&#xA;&#xA;div class=&#34;blog-signature&#34;&#xD;&#xA;    div class=&#34;sig-content&#34;&#xD;&#xA;        pThanks for reading! If you found this helpful:/p&#xD;&#xA;        div class=&#34;sig-links&#34;&#xD;&#xA;            a href=&#34;https://ko-fi.com/kaisbettaieb&#34; class=&#34;kofi-btn&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;☕ Buy me a coffee/a&#xD;&#xA;            span class=&#34;divider&#34;or/span&#xD;&#xA;            a href=&#34;https://github.com/kaisbettaieb&#34; class=&#34;text-link&#34;Check my Code/a&#xD;&#xA;            span class=&#34;divider&#34;or/span&#xD;&#xA;            a href=&#34;https://kbt.dev&#34; class=&#34;text-link&#34;More about me/a&#xD;&#xA;        /div&#xD;&#xA;    /div&#xD;&#xA;/div&#xD;&#xA;div id=&#34;comment-section&#34;/div&#xD;&#xA;&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://blog.kaisbettaieb.dev/tag:cusdis" class="hashtag"><span>#</span><span class="p-category">cusdis</span></a> <a href="https://blog.kaisbettaieb.dev/tag:comments" class="hashtag"><span>#</span><span class="p-category">comments</span></a> <a href="https://blog.kaisbettaieb.dev/tag:writefreely" class="hashtag"><span>#</span><span class="p-category">writefreely</span></a> <a href="https://blog.kaisbettaieb.dev/tag:javascript" class="hashtag"><span>#</span><span class="p-category">javascript</span></a></p>

<p>WriteFreely is beloved for its minimalism. However, fostering a community often requires a feedback loop. The challenge is adding comments without shattering that clean, distraction-free aesthetic.

I recently integrated <strong>Cusdis</strong>—a lightweight, open-source, and privacy-friendly comment system—into this blog. It aligns seamlessly with WriteFreely’s ethos: it requires no user tracking and adapts beautifully with a bit of custom CSS.</p>

<p>Here is how to implement a fully dynamic, styled comment section. This script ensures the widget loads <strong>only</strong> on individual posts (keeping your homepage pristine) and automatically respects dark mode.</p>

<h2 id="step-1-get-your-cusdis-id">Step 1: Get Your Cusdis ID</h2>

<p>First, you need a running Cusdis instance. You have two paths:</p>
<ul><li><strong>Cloud:</strong> Create a free account at <a href="https://cusdis.com">Cusdis.com</a>.</li>
<li><strong>Self-Hosted:</strong> Host it yourself on a VPS (using Coolify or Docker) for 100% data ownership.</li></ul>

<p>Once you have access to your dashboard, copy your <strong>App ID</strong> (e.g., <code>f42fd160-a4f2-...</code>). You will need this for the JavaScript configuration below.</p>

<h2 id="step-2-the-logic-why-custom-js">Step 2: The Logic (Why Custom JS?)</h2>

<p>Since WriteFreely lacks native support for editing individual post templates, we cannot simply paste the standard Cusdis embed code. A static embed cannot automatically fetch the unique Page ID or Title for every article.</p>

<p>Instead, we use Custom JavaScript to:</p>
<ol><li><strong>Verify context:</strong> Check if the user is viewing a single post (ignoring the homepage).</li>
<li><strong>Build the UI:</strong> Dynamically append a “Comments” container to the bottom of the article.</li>
<li><strong>Inject the Widget:</strong> Initialize Cusdis with the correct metadata (ID, URL, Title).</li>
<li><strong>Style:</strong> Force the widget into “Dark Mode” to match the theme.</li></ol>

<h2 id="step-3-the-javascript-code">Step 3: The JavaScript Code</h2>

<p>Navigate to your WriteFreely dashboard, go to <strong>Customize &gt; Custom JavaScript</strong>, and paste the code below.</p>

<blockquote><p><strong>Note:</strong> Be sure to replace <code>&#39;YOUR-APP-ID-HERE&#39;</code> with the actual ID you copied in Step 1.</p></blockquote>

<pre><code class="language-javascript">(function() {
    // Only run this script if we are on a post page (body#post)
    // This prevents the comments from loading on the homepage or collections.
    var postBody = document.querySelector(&#39;body#post&#39;);
    
    if (postBody) {
        // 1. Create the container div for Cusdis
        var cusdisContainer = document.createElement(&#39;div&#39;);
        cusdisContainer.id = &#39;cusdis_thread&#39;;
        cusdisContainer.dataset.host = &#39;[https://cusdis.com](https://cusdis.com)&#39;;
        cusdisContainer.dataset.appId = &#39;YOUR-APP-ID-HERE&#39;; // PASTE YOUR ID HERE
        cusdisContainer.dataset.pageId = window.location.pathname;
        cusdisContainer.dataset.pageUrl = window.location.href;
        cusdisContainer.dataset.pageTitle = document.title;
        cusdisContainer.dataset.theme = &#39;dark&#39;; // Forces dark mode

        // 2. Append the container to the article or main content area
        // Note: &#39;article&#39; is standard in most WriteFreely themes. 
        var article = document.querySelector(&#39;article&#39;);
        if (article) {
            article.appendChild(cusdisContainer);
        }

        // 3. Load the Cusdis SDK asynchronously
        var script = document.createElement(&#39;script&#39;);
        script.src = &#39;[https://cusdis.com/js/cusdis.es.js](https://cusdis.com/js/cusdis.es.js)&#39;;
        script.async = true;
        script.defer = true;
        document.body.appendChild(script);
    }
})();
</code></pre>

<h2 id="step-4-the-styling">Step 4: The styling</h2>

<p>Now that the functionality is in place, let&#39;s style the container to look like a piece of retro-futuristic hardware. We want a monospace font, a “console” border, and a distinct separation from the rest of your content.</p>

<p>Navigate to <strong>Customize &gt; Custom CSS</strong> and add the following:</p>

<pre><code class="language-css">
/* --- VISIBILITY RULES of COMMENT SECTION --- */
body#collection #comment-section,
body#subpage #comment-section {
    display: none !important;
}

body#post #comment-section {
    display: block !important;
}

/* --- END VISIBILITY RULES OF COMMENT SECTION -- */
/* --- CUSDIS COMMENT SECTION FIXES --- */
#comment-section {
    margin-top: 3rem; /* space from article content */
    width: 100%;
    max-width: 100%;
}

/* Cusdis iframe container */
#cusdis_thread {
    width: 100% !important;
    min-height: 500px; /* initial height */
    border-radius: 8px;
    overflow: hidden; /* remove inner scrollbars */
    background-color: var(--surface-color) !important;
    border: 1px solid var(--border-color) !important;
    box-shadow: 0 0 15px rgba(0, 0, 0, 0.4);
}

/* Make the iframe responsive */
#cusdis_thread iframe {
    width: 100% !important;
    min-height: 500px !important;
    height: auto !important;
    border: none !important;
}

/* Dark theme for Cusdis content */
#cusdis_thread {
    --cusdis-background: var(--surface-color);
    --cusdis-text-color: var(--text-color);
    --cusdis-accent-color: var(--accent-color);
    --cusdis-border-color: var(--border-color);
    --cusdis-input-background: #222; /* input background */
    --cusdis-input-text-color: #eee; /* input text */
    --cusdis-button-background: var(--accent-color);
    --cusdis-button-text-color: #000;
}

/* Force Cusdis to expand to fit content (if Cusdis JS supports it) */
#cusdis_thread iframe {
    height: 100% !important;
}

/* Inputs and textareas */
#cusdis_thread textarea,
#cusdis_thread input {
    background-color: #222 !important;
    color: #eee !important;
    border: 1px solid var(--border-color) !important;
    border-radius: 6px;
    padding: 8px;
}

/* Submit button */
#cusdis_thread button {
    background-color: var(--accent-color) !important;
    color: #000 !important;
    font-weight: 700;
    border-radius: 6px !important;
    padding: 6px 14px !important;
    border: none !important;
    cursor: pointer;
    transition: all 0.2s ease;
}

#cusdis_thread button:hover {
    background-color: #3aa8d1 !important;
    box-shadow: 0 0 12px rgba(98, 200, 243, 0.5) !important;
    color: #000 !important;
}

/* Scrollbar styling for iframe if it still scrolls */
#cusdis_thread iframe::-webkit-scrollbar {
    width: 8px;
}

#cusdis_thread iframe::-webkit-scrollbar-thumb {
    background-color: #555;
    border-radius: 4px;
}

#cusdis_thread iframe::-webkit-scrollbar-track {
    background-color: #111;
}

/* Optional: smooth font for Cusdis */
#cusdis_thread,
#cusdis_thread iframe {
    font-family: &#39;Nunito&#39;, sans-serif !important;
}

</code></pre>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>And that is it. You now have a lightweight, privacy-respecting comment section that loads exclusively on your articles.</p>

<p>By injecting the widget via JavaScript, you bypass WriteFreely&#39;s template limitations while maintaining total control over the look and feel. Your readers can now interact with your content in a space that feels like a natural extension of your blog&#39;s “hacker” aesthetic—no tracking pixels attached.</p>

<div class="blog-signature">
    <div class="sig-content">
        <p>Thanks for reading! If you found this helpful:</p>
        <div class="sig-links">
            <a href="https://ko-fi.com/kaisbettaieb" class="kofi-btn" target="_blank">☕ Buy me a coffee</a>
            <span class="divider">or</span>
            <a href="https://github.com/kaisbettaieb" class="text-link">Check my Code</a>
            <span class="divider">or</span>
            <a href="https://kbt.dev" class="text-link">More about me</a>
        </div>
    </div>
</div>
<div id="comment-section"></div>
]]></content:encoded>
      <guid>https://blog.kaisbettaieb.dev/adding-comments-to-writefreely-with-cusdis</guid>
      <pubDate>Tue, 13 Jan 2026 14:51:59 +0000</pubDate>
    </item>
    <item>
      <title>Deploying WriteFreely on a Coolify VPS with MySQL</title>
      <link>https://blog.kaisbettaieb.dev/deploying-writefreely-on-a-coolify-vps-with-mysql</link>
      <description>&lt;![CDATA[#coolify #writefreely #vps #mysql&#xA;&#xA;I currently run a Coolify instance on my VPS and recently decided to host a lightweight blogging platform. I managed to deploy WriteFreely using the open-source algernon/writefreely Docker image. While the initial deployment was straightforward, configuring the storage and database correctly required some specific steps. Here is a walkthrough of how I achieved a stable setup.&#xA;!--more--&#xA;&#xA;1. The Docker Configuration&#xA;&#xA;First, I pulled the Docker image and deployed it via Coolify. The primary challenge was configuring persistent storage. By default, if you restart the container, you lose your configuration, which makes the service unusable in production.&#xA;&#xA;To address this, I configured Coolify to create a persistent volume. I mapped a volume named wfdata to /data inside the container. This step is critical because the config.ini file needs to reside in this directory to persist across restarts.&#xA;&#xA;2. Adding a Standalone MySQL Database&#xA;&#xA;I preferred not to use the default SQLite engine, so I decided to provision a standalone MySQL database service within Coolify. The process is remarkably straightforward: you simply create a new resource, select MySQL, and let Coolify handle the password generation.&#xA;&#xA;The tricky part was establishing the connection between the app and the database. I initially encountered &#34;Access Denied&#34; errors because I was attempting to use the default mysql user. Once I updated the credentials in the configuration file to use the root user (or a dedicated user with proper permissions), the connection worked perfectly.&#xA;&#xA;3. Linking the Components&#xA;&#xA;To ensure WriteFreely correctly reads from the persistent volume, I had to modify the startup command. I couldn&#39;t immediately locate the command field in the UI, so I utilized the Docker Compose configuration to override the entry point:&#xA;&#xA;/bin/sh -c &#34;./writefreely -c /data/config.ini&#34;&#xA;&#xA;Additionally, I ran the configuration wizard directly inside the terminal to generate the initial config.ini. I made sure to point the encryption keys and database paths specifically to /data.&#xA;&#xA;Conclusion&#xA;&#xA;I now have a fully functional WriteFreely instance running on my VPS, complete with persistent storage and a robust MySQL backend. It is a highly efficient and self-contained setup.&#xA;&#xA;div class=&#34;blog-signature&#34;&#xD;&#xA;    div class=&#34;sig-content&#34;&#xD;&#xA;        pThanks for reading! If you found this helpful:/p&#xD;&#xA;        div class=&#34;sig-links&#34;&#xD;&#xA;            a href=&#34;https://ko-fi.com/kaisbettaieb&#34; class=&#34;kofi-btn&#34; target=&#34;blank&#34; rel=&#34;noopener&#34;☕ Buy me a coffee/a&#xD;&#xA;            span class=&#34;divider&#34;or/span&#xD;&#xA;            a href=&#34;https://github.com/kaisbettaieb&#34; class=&#34;text-link&#34;Check my Code/a&#xD;&#xA;            span class=&#34;divider&#34;or/span&#xD;&#xA;            a href=&#34;https://kbt.dev&#34; class=&#34;text-link&#34;More about me/a&#xD;&#xA;        /div&#xD;&#xA;    /div&#xD;&#xA;/div&#xD;&#xA;div id=&#34;comment-section&#34;/div&#xD;&#xA;&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://blog.kaisbettaieb.dev/tag:coolify" class="hashtag"><span>#</span><span class="p-category">coolify</span></a> <a href="https://blog.kaisbettaieb.dev/tag:writefreely" class="hashtag"><span>#</span><span class="p-category">writefreely</span></a> <a href="https://blog.kaisbettaieb.dev/tag:vps" class="hashtag"><span>#</span><span class="p-category">vps</span></a> <a href="https://blog.kaisbettaieb.dev/tag:mysql" class="hashtag"><span>#</span><span class="p-category">mysql</span></a></p>

<p>I currently run a <strong>Coolify</strong> instance on my VPS and recently decided to host a lightweight blogging platform. I managed to deploy <strong>WriteFreely</strong> using the open-source <code>algernon/writefreely</code> Docker image. While the initial deployment was straightforward, configuring the storage and database correctly required some specific steps. Here is a walkthrough of how I achieved a stable setup.
</p>

<h2 id="1-the-docker-configuration">1. The Docker Configuration</h2>

<p>First, I pulled the Docker image and deployed it via Coolify. The primary challenge was configuring <strong>persistent storage</strong>. By default, if you restart the container, you lose your configuration, which makes the service unusable in production.</p>

<p>To address this, I configured Coolify to create a persistent volume. I mapped a volume named <code>wf_data</code> to <code>/data</code> inside the container. This step is critical because the <code>config.ini</code> file needs to reside in this directory to persist across restarts.</p>

<h2 id="2-adding-a-standalone-mysql-database">2. Adding a Standalone MySQL Database</h2>

<p>I preferred not to use the default SQLite engine, so I decided to <strong>provision a standalone MySQL database service</strong> within Coolify. The process is remarkably straightforward: you simply create a new resource, select MySQL, and let Coolify handle the password generation.</p>

<p>The tricky part was establishing the connection between the app and the database. I initially encountered “Access Denied” errors because I was attempting to use the default <code>mysql</code> user. Once I updated the credentials in the configuration file to use the <code>root</code> user (or a dedicated user with proper permissions), the connection worked perfectly.</p>

<h2 id="3-linking-the-components">3. Linking the Components</h2>

<p>To ensure WriteFreely correctly reads from the persistent volume, I had to modify the startup command. I couldn&#39;t immediately locate the command field in the UI, so I utilized the <strong>Docker Compose</strong> configuration to override the entry point:</p>

<pre><code class="language-bash">/bin/sh -c &#34;./writefreely -c /data/config.ini&#34;
</code></pre>

<p>Additionally, I ran the configuration wizard directly inside the terminal to generate the initial <code>config.ini</code>. I made sure to point the encryption keys and database paths specifically to <code>/data</code>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>I now have a fully functional WriteFreely instance running on my VPS, complete with persistent storage and a robust MySQL backend. It is a highly efficient and self-contained setup.</p>

<div class="blog-signature">
    <div class="sig-content">
        <p>Thanks for reading! If you found this helpful:</p>
        <div class="sig-links">
            <a href="https://ko-fi.com/kaisbettaieb" class="kofi-btn" target="_blank">☕ Buy me a coffee</a>
            <span class="divider">or</span>
            <a href="https://github.com/kaisbettaieb" class="text-link">Check my Code</a>
            <span class="divider">or</span>
            <a href="https://kbt.dev" class="text-link">More about me</a>
        </div>
    </div>
</div>
<div id="comment-section"></div>
]]></content:encoded>
      <guid>https://blog.kaisbettaieb.dev/deploying-writefreely-on-a-coolify-vps-with-mysql</guid>
      <pubDate>Fri, 09 Jan 2026 09:27:18 +0000</pubDate>
    </item>
  </channel>
</rss>