<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: DockSky</title>
    <description>The latest articles on DEV Community by DockSky (@docksky).</description>
    <link>https://dev.to/docksky</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3966883%2Ff2caf632-e8bc-46df-8ef1-b044fe808177.png</url>
      <title>DEV Community: DockSky</title>
      <link>https://dev.to/docksky</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/docksky"/>
    <language>en</language>
    <item>
      <title>How I Gave My AI Persistent Memory: From Markdown Hacks to MCP</title>
      <dc:creator>DockSky</dc:creator>
      <pubDate>Fri, 26 Jun 2026 05:59:27 +0000</pubDate>
      <link>https://dev.to/docksky/how-i-gave-my-ai-persistent-memory-from-markdown-hacks-to-mcp-257i</link>
      <guid>https://dev.to/docksky/how-i-gave-my-ai-persistent-memory-from-markdown-hacks-to-mcp-257i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;June 2026&lt;/strong&gt; - In 2025 I hacked together a markdown-file solution to avoid re-explaining my context every session. It worked. Up to a point. Here is why I ended up building something different.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The problem, still very real
&lt;/h2&gt;

&lt;p&gt;You know the scenario.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session 1:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You: "I'm working on a FastAPI API with MySQL, deployed on a VPS..."&lt;/li&gt;
&lt;li&gt;The AI: understands everything, codes perfectly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Session 2, the next day:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You: "Continue yesterday's work."&lt;/li&gt;
&lt;li&gt;The AI: "I don't have context on your project. Could you describe..."&lt;/li&gt;
&lt;li&gt;You: 😤&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a bug. It is the nature of LLMs: &lt;strong&gt;zero memory between sessions&lt;/strong&gt;. Every conversation starts from scratch.&lt;/p&gt;

&lt;p&gt;With a complex project (API, frontend, Docker, VPS, multiple repos), re-explaining the context takes &lt;strong&gt;10 to 15 minutes&lt;/strong&gt; per session. Over a week: an hour lost. Over a month: a full day of work gone.&lt;/p&gt;

&lt;p&gt;And the worst part: it is not the code that evaporates. It is the &lt;strong&gt;why&lt;/strong&gt;. The decisions, the traps you avoided, the paths you dropped. Exactly what separates "it compiles" from "it is maintainable."&lt;/p&gt;




&lt;h2&gt;
  
  
  My first answer: the &lt;code&gt;/AI/memory/&lt;/code&gt; folder (2025)
&lt;/h2&gt;

&lt;p&gt;A DIY solution. One folder per project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-project/
├── AI/
│   └── memory/
│       ├── project_context.md
│       ├── tech_stack.md
│       └── code_registry.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At session start:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Read the files in &lt;code&gt;/AI/memory/&lt;/code&gt; to load the context."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thirty seconds. The AI knows the project, the conventions, the architectural decisions. &lt;strong&gt;It worked.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For months I ran like this. Multiple repos. Multiple folders. Markdown everywhere. Then the cracks showed up.&lt;/p&gt;




&lt;h2&gt;
  
  
  The limits: why DIY is not enough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Manual maintenance
&lt;/h3&gt;

&lt;p&gt;Every new endpoint, every architecture decision: you have to &lt;strong&gt;remember&lt;/strong&gt; to update the files. When you juggle between sessions, especially with a brain that jumps from topic to topic, "do not forget to update" is precisely the starting problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drift
&lt;/h3&gt;

&lt;p&gt;A few weeks later, the markdown describes an architecture that &lt;strong&gt;no longer exists&lt;/strong&gt;. The AI works from stale information. You do not always notice until the prod bug or the incoherent PR.&lt;/p&gt;

&lt;h3&gt;
  
  
  Copy-paste persists
&lt;/h3&gt;

&lt;p&gt;You ask the AI to read local files. Or you copy the JSON yourself. It is not fluid. It is not reliable. And it does not scale when you go from one repo to four.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fragmentation
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;docksky-api&lt;/code&gt;, &lt;code&gt;docksky-web&lt;/code&gt;, &lt;code&gt;tda-assistant-ui&lt;/code&gt;, infra... Each repo its own &lt;code&gt;/AI/memory/&lt;/code&gt; folder. No overview. Impossible to switch context without reloading manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; the method treated the symptom, not the cause. Context lived in dead files, updated when I thought of it. That is, not often enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  MCP: what changes the game
&lt;/h2&gt;

&lt;p&gt;In 2025, Anthropic published the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;: an open standard for an AI agent to interact with external data sources in a structured way.&lt;/p&gt;

&lt;p&gt;Before:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The AI reads files I prepared."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The AI calls tools and interacts directly with my knowledge base."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It reads context. It writes a note. It creates a journal entry. &lt;strong&gt;Without my intervention.&lt;/strong&gt; No copy-paste. No asking whether I remembered to update the markdown.&lt;/p&gt;

&lt;p&gt;The difference is not just technical. It is &lt;strong&gt;structural&lt;/strong&gt;. Context is no longer a static file. It is a living system.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I built: DockSky
&lt;/h2&gt;

&lt;p&gt;I answered the problem by building &lt;strong&gt;DockSky&lt;/strong&gt;. Not another chatbot, but a &lt;strong&gt;shared external brain&lt;/strong&gt; between me and my AI.&lt;/p&gt;

&lt;p&gt;A desktop app plus MCP server. When Cursor or Claude connects via MCP, the agent gets native tools to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;list my projects&lt;/li&gt;
&lt;li&gt;load a specific project's context&lt;/li&gt;
&lt;li&gt;read my &lt;strong&gt;facets&lt;/strong&gt; (decisions, traps, patterns, technical info)&lt;/li&gt;
&lt;li&gt;create a &lt;strong&gt;journal&lt;/strong&gt; entry&lt;/li&gt;
&lt;li&gt;update an action or a step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two complementary layers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Facets&lt;/th&gt;
&lt;th&gt;Journal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What is worth &lt;strong&gt;keeping&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;What I &lt;strong&gt;did&lt;/strong&gt;, day by day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;When&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Thematic, long term&lt;/td&gt;
&lt;td&gt;Chronological&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TRAP: missing UpdateSourceTrigger on this binding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Fixed login bug, tested on staging"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The base is &lt;strong&gt;persistent&lt;/strong&gt;. It is the same one I use daily in the app. When the AI notes a decision, it writes where I will find it tomorrow, not in a forgotten markdown file at the bottom of a repo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concrete results (dogfooding, several months)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Starting a session on an active project: &lt;strong&gt;under 30 seconds&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The AI no longer asks "remind me of the architecture"&lt;/li&gt;
&lt;li&gt;Decisions from one session are available in the next&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero&lt;/strong&gt; markdown files to maintain by hand&lt;/li&gt;
&lt;li&gt;Git commits feed the journal automatically (post-commit hook)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not magic. It is &lt;strong&gt;discipline reinforced by the tool&lt;/strong&gt;: one point done, one trace in DockSky, move to the next. Tech does not replace the habit. It makes it cheaper.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AI memory is not in the model&lt;/strong&gt; — it must be structured and reloadable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local markdown = good prototype, bad product&lt;/strong&gt; — human maintenance is a guaranteed failure point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP = the right level of abstraction&lt;/strong&gt; — standardized tools, not artisanal copy-paste.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intent over code&lt;/strong&gt; — what matters is capturing the &lt;em&gt;why&lt;/em&gt; as you go, not archiving 400 KB of transcript.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A recent dev.to post put it better than I could: &lt;em&gt;"Agents write code, but they don't remember."&lt;/em&gt; Exactly. Generation is solved. Context persistence is not.&lt;/p&gt;




&lt;h2&gt;
  
  
  The minimal workflow (no DockSky required)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 specific point → done → 2 lines of trace → next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A journal. A decisions file. Even a &lt;code&gt;DECISIONS.md&lt;/code&gt; README. What matters: &lt;strong&gt;do not let intent evaporate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;DockSky is that workflow turned into a system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Does this problem sound familiar?
&lt;/h2&gt;

&lt;p&gt;DockSky is a project I am building because I needed it: a brain that jumps, multiple repos, AI sessions that restart from zero every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I am not looking for customers.&lt;/strong&gt; I am looking for people who recognize this story and want to &lt;strong&gt;co-build&lt;/strong&gt;, not just use a tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I am offering
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;crew&lt;/strong&gt; (Équipage): a private space (Discord plus Pro access while we build) where we talk about product direction. Sort ideas. Make design calls. Say what works or blocks in real usage. Not a support channel. A &lt;strong&gt;kitchen&lt;/strong&gt; where founders think before anything goes public.&lt;/p&gt;

&lt;p&gt;You do not need to be an expert at everything. What I care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you live the same context / AI memory problem;&lt;/li&gt;
&lt;li&gt;you want to contribute: field feedback, code, docs, UX ideas;&lt;/li&gt;
&lt;li&gt;you accept we are building, not shipping a finished product.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What this is not
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Not a "download for free" pitch&lt;/li&gt;
&lt;li&gt;Not an obligation to test X hours per week&lt;/li&gt;
&lt;li&gt;Not open enrollment. The crew opens &lt;strong&gt;by invitation&lt;/strong&gt;, after I read your application&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to reach me
&lt;/h3&gt;

&lt;p&gt;If this article resonated, get in touch. Comment here, or apply at &lt;a href="https://docksky.fr/equipage" rel="noopener noreferrer"&gt;docksky.fr/equipage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tell me &lt;strong&gt;what hooked you&lt;/strong&gt; and &lt;strong&gt;what you would want to bring to the table&lt;/strong&gt;. No marketing funnel. I read every message myself.&lt;/p&gt;

&lt;p&gt;No commitment. Just see if we speak the same language.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;How do you handle AI context between sessions today: markdown, rules files, something else? I would genuinely like to hear in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>mcp</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I Set Up HAProxy + ProxySQL on a Single OVH VPS for My Solo SaaS</title>
      <dc:creator>DockSky</dc:creator>
      <pubDate>Tue, 23 Jun 2026 05:31:05 +0000</pubDate>
      <link>https://dev.to/docksky/how-i-set-up-haproxy-proxysql-on-a-single-ovh-vps-for-my-solo-saas-5ec7</link>
      <guid>https://dev.to/docksky/how-i-set-up-haproxy-proxysql-on-a-single-ovh-vps-for-my-solo-saas-5ec7</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I needed to expose MySQL on the internet without getting scanned into oblivion within 48 hours. I stacked HAProxy in front of ProxySQL in front of MySQL. It works. I also spent six hours fighting a proxy that reads its config file once in its life, then pretends it never met you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem (or: why I didn't just buy an $80/month RDS)
&lt;/h2&gt;

&lt;p&gt;I'm building &lt;strong&gt;DockSky&lt;/strong&gt;, an indie SaaS, solo, on an &lt;strong&gt;8 GB OVH VPS&lt;/strong&gt; for about €14/month. No ops team. No "managed database" budget. Just me, Docker, and optimism.&lt;/p&gt;

&lt;p&gt;Except DockSky isn't just a REST API with Postgres behind it. The product is &lt;strong&gt;managed multi-tenant MySQL&lt;/strong&gt;: each customer gets their own MySQL user, their own credentials, real isolation. Not a &lt;code&gt;tenant_id&lt;/code&gt; column and a prayer.&lt;/p&gt;

&lt;p&gt;For that to work, I need &lt;strong&gt;external MySQL access&lt;/strong&gt; on port &lt;strong&gt;6033&lt;/strong&gt;, without leaving MySQL wide open on the internet like a vending machine at 3 a.m.&lt;/p&gt;

&lt;p&gt;Before ProxySQL, I already had &lt;strong&gt;HAProxy&lt;/strong&gt; forwarding traffic to MySQL. It worked. In the sense that nobody had tried &lt;code&gt;SELECT * FROM users WHERE 1=1&lt;/code&gt; on me yet.&lt;/p&gt;

&lt;p&gt;What I was missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; at the network layer (a bot opening 500 connections is trivial)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection pooling&lt;/strong&gt; (my VPS has 8 GB of RAM, not a datacenter)&lt;/li&gt;
&lt;li&gt;A layer to &lt;strong&gt;route multi-tenant users&lt;/strong&gt; without hand-editing MySQL every time someone signs up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So: a network gateway &lt;strong&gt;and&lt;/strong&gt; a SQL gateway. Two tools, two jobs. Like a bouncer at the club door and another at the VIP bar. Except nobody's paying cover yet. Still in beta.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture (Post-it edition)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet (port 6033, the only MySQL port open in UFW)
    ↓
HAProxy          → "Be nice, but not too many connections"
    ↓
ProxySQL         → "Does your user exist? Is this query acceptable?"
    ↓
MySQL 8.x        → localhost only, like a well-behaved introvert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the side, &lt;strong&gt;Traefik&lt;/strong&gt; handles HTTPS for admin interfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;haproxy-stats.docksky.fr&lt;/code&gt; to see if everything's green&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxysql-admin.docksky.fr&lt;/code&gt; to stare at tables I only half understand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created the subdomains via the &lt;strong&gt;OVH DNS API&lt;/strong&gt;. Because clicking 40 times in the OVH panel is the kind of task that makes me want to quit software and open a food truck.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I actually deployed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Lock down MySQL (finally)
&lt;/h3&gt;

&lt;p&gt;MySQL only listens locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:3306:3306"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want my data? Get in line. Like the post office, but with rate limiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. HAProxy: friendly but firm bouncer
&lt;/h3&gt;

&lt;p&gt;Frontend on &lt;code&gt;*:6033&lt;/code&gt;, backend to &lt;code&gt;proxysql:6032&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stick-table type ip size 100k expire 30s store conn_cur,conn_rate(3s)
tcp-request connection track-sc0 src
tcp-request connection track-sc1 src
tcp-request connection reject if { sc0_conn_cur ge 5 }
tcp-request connection reject if { sc1_conn_rate ge 10 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Human translation: &lt;strong&gt;5 simultaneous connections max per IP&lt;/strong&gt;, &lt;strong&gt;10 new connections max every 3 seconds&lt;/strong&gt;. Enough for an honest client. Not enough for a script kiddie with a &lt;code&gt;for&lt;/code&gt; loop and too much free time.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. ProxySQL: the six-hour side quest
&lt;/h3&gt;

&lt;p&gt;Docker service with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;template&lt;/strong&gt; &lt;code&gt;proxysql.cnf.template&lt;/code&gt; (no secrets in git, I learned that lesson earlier)&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;custom entrypoint&lt;/strong&gt; that generates config at startup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tmpfs&lt;/strong&gt; on &lt;code&gt;/var/lib/proxysql&lt;/code&gt; (more on that, it's the plot twist)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;beta user sync&lt;/strong&gt; from MySQL into ProxySQL at boot (otherwise every new DockSky customer means a manual intervention, and I don't have a support team)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entrypoint waits for ProxySQL to be ready, then syncs users. Because ProxySQL takes &lt;strong&gt;25 to 30 seconds&lt;/strong&gt; to wake up. Like me, but without coffee.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Version everything before the VPS explodes
&lt;/h3&gt;

&lt;p&gt;I spun up a &lt;strong&gt;&lt;code&gt;docksky-infra&lt;/code&gt;&lt;/strong&gt; repo with a full disaster recovery procedure. Because one day the VPS will die (Murphy's Law, Docker edition) and I don't want to rebuild this from memory on a Sunday night.&lt;/p&gt;




&lt;h2&gt;
  
  
  What broke (the part Dev.to loves)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue #1: Docker Compose 1.29.2
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; &lt;code&gt;KeyError: 'ContainerConfig'&lt;/code&gt;. Couldn't recreate containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My reaction:&lt;/strong&gt; "It's not me, it's Docker." (Spoiler: it was Docker.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; upgrade to &lt;strong&gt;Docker Compose v2&lt;/strong&gt;. &lt;code&gt;docker compose&lt;/code&gt;, no hyphen. Like switching from a bike to a car, except the car costs fewer nerves.&lt;/p&gt;




&lt;h3&gt;
  
  
  Issue #2: ProxySQL and the ghost config file
&lt;/h3&gt;

&lt;p&gt;The trap that ate &lt;strong&gt;six hours&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First boot: everything works. I think I'm a genius.&lt;br&gt;&lt;br&gt;
Container restart: &lt;code&gt;Access denied&lt;/code&gt;. I think I'm an idiot.&lt;/p&gt;

&lt;p&gt;Official docs, one killer sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"After first startup the DB file is used instead of the config file"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ProxySQL creates an internal SQLite database on first boot. Then it &lt;strong&gt;ignores your &lt;code&gt;.cnf&lt;/code&gt;&lt;/strong&gt; like an ex ignoring your texts.&lt;/p&gt;

&lt;p&gt;I tried:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--initial&lt;/code&gt; → nope&lt;/li&gt;
&lt;li&gt;deleting the volume → the DB comes back, poltergeist style&lt;/li&gt;
&lt;li&gt;native env vars → ProxySQL doesn't care&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What actually works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;tmpfs&lt;/strong&gt; on &lt;code&gt;/var/lib/proxysql&lt;/code&gt;. SQLite dies on every restart, ProxySQL is &lt;em&gt;forced&lt;/em&gt; to reread the template&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;custom entrypoint&lt;/strong&gt; generates &lt;code&gt;/etc/proxysql.cnf&lt;/code&gt; with real passwords (&lt;code&gt;sed&lt;/code&gt; first, then &lt;code&gt;perl&lt;/code&gt; when &lt;code&gt;sed&lt;/code&gt; started giving me side-eye)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;wait 30 seconds&lt;/strong&gt; before testing. Otherwise you think it's broken when it's just slow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Trade-off:&lt;/strong&gt; any live change in ProxySQL (&lt;code&gt;INSERT INTO mysql_users&lt;/code&gt;, etc.) vanishes on restart. Source of truth is the template. Not the moody SQLite database.&lt;/p&gt;


&lt;h3&gt;
  
  
  Issue #3: HAProxy declares ProxySQL dead
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Logs:&lt;/strong&gt; &lt;code&gt;Access denied for user 'monitor'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I'd set &lt;code&gt;option mysql-check user monitor&lt;/code&gt;. Except in ProxySQL, &lt;code&gt;monitor&lt;/code&gt; is for watching MySQL backends, not for saying "hey, you alive?" to the proxy itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;option tcp-check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port open? It's UP. Stoic philosophy for healthchecks.&lt;/p&gt;




&lt;h3&gt;
  
  
  Issue #4: Two stick-tables, one angry HAProxy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; crash loop, &lt;code&gt;stick-table name 'mysql-in' conflicts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I'd declared two separate tables. HAProxy doesn't share well. One table, two counters. Like a Paris studio, but for IPs.&lt;/p&gt;




&lt;h3&gt;
  
  
  Issue #5: &lt;code&gt;envsubst&lt;/code&gt; doesn't exist in Alpine
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Logs:&lt;/strong&gt; &lt;code&gt;envsubst: command not found&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The ProxySQL image is minimal. I'd copied a 2019 Stack Overflow tutorial. Classic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; &lt;code&gt;sed&lt;/code&gt;, then &lt;code&gt;perl&lt;/code&gt;. The current wrapper uses &lt;code&gt;perl&lt;/code&gt;. At some point you stop fighting &lt;code&gt;sed&lt;/code&gt; and accept defeat with dignity.&lt;/p&gt;




&lt;h3&gt;
  
  
  Issue #6: The silent restart (a few weeks later)
&lt;/h3&gt;

&lt;p&gt;ProxySQL refused connections after a restart. No noise. No email. No "sorry boss."&lt;/p&gt;

&lt;p&gt;I caught it in metrics within &lt;strong&gt;5 minutes&lt;/strong&gt;. Fixed in &lt;strong&gt;30&lt;/strong&gt;. That's exactly why I have healthchecks and a dashboard. Not to look good on a "our stack" slide.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's running today
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MySQL 8.4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;✅ localhost only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ProxySQL 2.7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pooling + multi-tenant users&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HAProxy 2.8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Network rate limiting&lt;/td&gt;
&lt;td&gt;✅ healthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Traefik&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTPS admin + rest of DockSky&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;UFW:&lt;/strong&gt; only &lt;strong&gt;6033&lt;/strong&gt; is open for MySQL. Everything else goes through Traefik or stays on localhost. My security policy is "paranoid, but not paranoid enough to shut everything down and work from a Raspberry Pi in a closet."&lt;/p&gt;




&lt;h2&gt;
  
  
  Is it worth it for a solo SaaS?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Honestly:&lt;/strong&gt; it depends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Yes if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you want multi-tenant MySQL without paying €80/month for managed DB&lt;/li&gt;
&lt;li&gt;you'll read the docs when things break (and they will)&lt;/li&gt;
&lt;li&gt;you document everything, because &lt;em&gt;future you&lt;/em&gt; remembers nothing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you want to sleep without thinking about ProxySQL&lt;/li&gt;
&lt;li&gt;you have 50 customers and no monitoring&lt;/li&gt;
&lt;li&gt;you hate proxies with a SQLite personality disorder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For DockSky today, it's the right trade-off. A €14 VPS, a stack I understand layer by layer, and an infra repo I can rebuild from scratch if OVH decides on a Tuesday that my server has lived its best life.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Upgrade your tooling before adding a critical service.&lt;/strong&gt; Docker Compose v2 saved me before ProxySQL even showed up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the official docs.&lt;/strong&gt; The SQLite sentence was there. I just took 4 hours to find it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple healthchecks beat clever ones.&lt;/strong&gt; TCP check is ugly but works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wait 30 seconds.&lt;/strong&gt; ProxySQL isn't lazy. It's… deliberate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version everything.&lt;/strong&gt; Shout-out to my dated &lt;code&gt;docker-compose.yml&lt;/code&gt; backup from November 17, 2025.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor.&lt;/strong&gt; A proxy that dies quietly is a proxy you discover when a customer DMs you.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The punchline
&lt;/h2&gt;

&lt;p&gt;Here's the part that still makes me laugh.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;original project&lt;/strong&gt; this stack was built for? I abandoned it. Dead. RIP. Somewhere in a folder with a name I try not to open.&lt;/p&gt;

&lt;p&gt;But HAProxy, ProxySQL, the OVH VPS, the disaster recovery repo, the "wait 30 seconds or you'll think it's broken" wisdom? All of that &lt;strong&gt;got recycled&lt;/strong&gt; into DockSky. Same infrastructure, different dream. Like keeping the engine from a car you totaled and dropping it into something that actually runs.&lt;/p&gt;

&lt;p&gt;So no, I wouldn't recommend building this stack just to abandon the product. But if you're a solo dev who's already paid the tuition in broken healthchecks and angry SQLite databases, you might as well &lt;strong&gt;reuse the homework&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Questions? I'm at &lt;a href="https://docksky.fr" rel="noopener noreferrer"&gt;docksky.fr&lt;/a&gt;. Contact form. No Telegram bot that replies "have you tried turning it off and on again."&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Stack: OVH VPS 8 GB · Debian · Docker Compose v2 · Traefik · HAProxy 2.8 · ProxySQL 2.7 · MySQL 8.4 · lots of logs&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>infrastructure</category>
      <category>saas</category>
    </item>
  </channel>
</rss>
