<?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: El Housseine Jaafari</title>
    <description>The latest articles on DEV Community by El Housseine Jaafari (@jefferyhus).</description>
    <link>https://dev.to/jefferyhus</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F114756%2F1cc5bf8e-1822-4af4-95e0-247b97f0824d.jpg</url>
      <title>DEV Community: El Housseine Jaafari</title>
      <link>https://dev.to/jefferyhus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jefferyhus"/>
    <language>en</language>
    <item>
      <title>Building an Engineering &amp; Security News Aggregator (10 Sources, No APIs)</title>
      <dc:creator>El Housseine Jaafari</dc:creator>
      <pubDate>Wed, 01 Apr 2026 18:10:32 +0000</pubDate>
      <link>https://dev.to/jefferyhus/building-an-engineering-security-news-aggregator-10-sources-no-apis-1dc9</link>
      <guid>https://dev.to/jefferyhus/building-an-engineering-security-news-aggregator-10-sources-no-apis-1dc9</guid>
      <description>&lt;p&gt;We built a curated engineering and security news aggregator that pulls from 10 high-signal sources, deduplicates content, and updates every 6 hours.&lt;/p&gt;

&lt;p&gt;No paid APIs. No scraping. No login. Just clean, structured news for developers.&lt;/p&gt;

&lt;p&gt;This post breaks down exactly how it works.&lt;/p&gt;




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

&lt;p&gt;A lightweight news wire combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hacker News
&lt;/li&gt;
&lt;li&gt;Lobsters
&lt;/li&gt;
&lt;li&gt;InfoQ
&lt;/li&gt;
&lt;li&gt;Cloudflare Blog
&lt;/li&gt;
&lt;li&gt;Krebs on Security
&lt;/li&gt;
&lt;li&gt;The Hacker News (Security)
&lt;/li&gt;
&lt;li&gt;NIST NVD (vulnerabilities)
&lt;/li&gt;
&lt;li&gt;GitHub Blog
&lt;/li&gt;
&lt;li&gt;OpenAI Blog
&lt;/li&gt;
&lt;li&gt;Anthropic Research
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal: &lt;strong&gt;high-quality signal, zero noise, zero cost&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Build This?
&lt;/h2&gt;

&lt;p&gt;Most engineering/news aggregators fail in one of these ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Too noisy (no curation)&lt;/li&gt;
&lt;li&gt;Too expensive (paid APIs)&lt;/li&gt;
&lt;li&gt;Too slow (manual updates)&lt;/li&gt;
&lt;li&gt;Too fragmented (you check 10 sites anyway)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single feed&lt;/li&gt;
&lt;li&gt;Fresh updates (but not real-time obsession)&lt;/li&gt;
&lt;li&gt;No operational cost&lt;/li&gt;
&lt;li&gt;No lock-in (no accounts, no tracking)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hono&lt;/strong&gt; (API layer)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drizzle ORM&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Postgres&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt; (frontend)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RSS feeds + Hacker News Firebase API&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  High-Level Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;           ┌───────────────┐
           │   RSS Feeds   │
           │ (9 sources)   │
           └──────┬────────┘
                  │
                  ▼
           ┌───────────────┐
           │ Fetch Workers │
           │ (every 6 hrs) │
           └──────┬────────┘
                  │
                  ▼
        ┌──────────────────────┐
        │ Normalize Articles   │
        │ title, url, date     │
        └─────────┬────────────┘
                  │
                  ▼
        ┌──────────────────────┐
        │ SHA-256 Deduplication│
        │ (based on URL)       │
        └─────────┬────────────┘
                  │
                  ▼
           ┌───────────────┐
           │   Postgres    │
           └──────┬────────┘
                  │
                  ▼
           ┌───────────────┐
           │   Hono API    │
           └──────┬────────┘
                  │
                  ▼
           ┌───────────────┐
           │   Next.js UI  │
           └───────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Data Sources
&lt;/h2&gt;

&lt;p&gt;We deliberately chose sources with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High editorial quality&lt;/li&gt;
&lt;li&gt;Low duplication between each other&lt;/li&gt;
&lt;li&gt;Stable RSS feeds or APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Breakdown
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hacker News&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;Real-time dev signal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lobsters&lt;/td&gt;
&lt;td&gt;RSS&lt;/td&gt;
&lt;td&gt;More technical discussions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InfoQ&lt;/td&gt;
&lt;td&gt;RSS&lt;/td&gt;
&lt;td&gt;Deep engineering content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Blog&lt;/td&gt;
&lt;td&gt;RSS&lt;/td&gt;
&lt;td&gt;Infra + performance insights&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Krebs on Security&lt;/td&gt;
&lt;td&gt;RSS&lt;/td&gt;
&lt;td&gt;Trusted security reporting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Hacker News&lt;/td&gt;
&lt;td&gt;RSS&lt;/td&gt;
&lt;td&gt;Security news (broader)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NIST NVD&lt;/td&gt;
&lt;td&gt;RSS/API&lt;/td&gt;
&lt;td&gt;Verified vulnerabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Blog&lt;/td&gt;
&lt;td&gt;RSS&lt;/td&gt;
&lt;td&gt;Platform + ecosystem updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI Blog&lt;/td&gt;
&lt;td&gt;RSS&lt;/td&gt;
&lt;td&gt;AI developments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic Research&lt;/td&gt;
&lt;td&gt;RSS&lt;/td&gt;
&lt;td&gt;AI + safety research&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Fetching Strategy
&lt;/h2&gt;

&lt;p&gt;We run a simple scheduled job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// every 6 hours&lt;/span&gt;
&lt;span class="nx"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 */6 * * *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchAllSources&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why every 6 hours?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeps content fresh&lt;/li&gt;
&lt;li&gt;Avoids unnecessary load&lt;/li&gt;
&lt;li&gt;Works well with RSS update frequencies&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Deduplication (Key Part)
&lt;/h2&gt;

&lt;p&gt;Different sources often post the same story.&lt;/p&gt;

&lt;p&gt;We solve this using &lt;strong&gt;SHA-256 hashing of URLs&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;hashUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why URL hashing?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fast&lt;/li&gt;
&lt;li&gt;Deterministic&lt;/li&gt;
&lt;li&gt;No fuzzy matching complexity&lt;/li&gt;
&lt;li&gt;Works across sources&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tradeoff
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Won’t catch rewritten articles with different URLs&lt;/li&gt;
&lt;li&gt;But avoids false positives (important for trust)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Normalization
&lt;/h2&gt;

&lt;p&gt;Each source has its own format. We normalize into a single shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the frontend simple and predictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  API Layer (Hono)
&lt;/h2&gt;

&lt;p&gt;Example endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/articles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;desc&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Minimal, fast, no overengineering.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frontend (Next.js)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Server-rendered list&lt;/li&gt;
&lt;li&gt;No login required&lt;/li&gt;
&lt;li&gt;No personalization&lt;/li&gt;
&lt;li&gt;Just chronological, deduplicated news&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Not real-time (by design)&lt;/li&gt;
&lt;li&gt;No personalization&lt;/li&gt;
&lt;li&gt;Deduplication is URL-based only&lt;/li&gt;
&lt;li&gt;Dependent on RSS availability&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What We’d Improve
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Smarter clustering (same story, different URLs)&lt;/li&gt;
&lt;li&gt;Tagging (infra, AI, security, etc.)&lt;/li&gt;
&lt;li&gt;Optional filters (without accounts)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The news wire is open to everyone:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://clawship.app/blog/engineering-security-news-wire" rel="noopener noreferrer"&gt;https://clawship.app/blog/engineering-security-news-wire&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Connect with Us
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Discord: &lt;a href="https://discord.gg/" rel="noopener noreferrer"&gt;https://discord.gg/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/" rel="noopener noreferrer"&gt;https://twitter.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;News Wire: &lt;a href="https://clawship.app/blog/engineering-security-news-wire" rel="noopener noreferrer"&gt;https://clawship.app/blog/engineering-security-news-wire&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Introducing MCCP (Managed Capabilities Control Plane): MCP + Browser Automation (Alpha) on Clawship</title>
      <dc:creator>El Housseine Jaafari</dc:creator>
      <pubDate>Fri, 13 Mar 2026 15:54:15 +0000</pubDate>
      <link>https://dev.to/jefferyhus/introducing-mccp-managed-capabilities-control-plane-mcp-browser-automation-alpha-on-clawship-2lpd</link>
      <guid>https://dev.to/jefferyhus/introducing-mccp-managed-capabilities-control-plane-mcp-browser-automation-alpha-on-clawship-2lpd</guid>
      <description>&lt;h2&gt;
  
  
  MCCP in one line
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MCCP (Managed Capabilities Control Plane)&lt;/strong&gt; is the layer in Clawship that connects your AI assistant to real-world capabilities—&lt;strong&gt;MCP (Model Context Protocol) servers&lt;/strong&gt;, integrations, and &lt;strong&gt;Browser Automation (Alpha)&lt;/strong&gt;—with safe, reviewable workflows.&lt;/p&gt;

&lt;p&gt;Try it here: &lt;strong&gt;&lt;a href="https://clawship.app" rel="noopener noreferrer"&gt;https://clawship.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqik5adwt66q2qidu1nxr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqik5adwt66q2qidu1nxr.png" alt="MCCP Browser Automation (Alpha) — Clawship" width="800" height="639"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Screenshot: MCCP Browser (Alpha) — launch authenticated browser sessions to automate platforms through your AI assistant.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why MCCP matters (beyond tool-calling demos)
&lt;/h2&gt;

&lt;p&gt;Shipping an AI agent to production usually breaks on the “boring” parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrations drift (schemas, tokens, rate limits)&lt;/li&gt;
&lt;li&gt;Different agents need different tools and permissions&lt;/li&gt;
&lt;li&gt;Teams want &lt;strong&gt;draft-first&lt;/strong&gt; outputs and explicit confirmation before publishing&lt;/li&gt;
&lt;li&gt;Some workflows are still UI-only (no reliable API)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MCCP exists to make those operational edges predictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  What you can do with MCCP
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Connect MCP servers (Model Context Protocol)
&lt;/h3&gt;

&lt;p&gt;MCP gives you a standardized way to expose tools (SaaS apps, internal services, custom servers) to your agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example MCP config (conceptual):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"list"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"notion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@notionhq/mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linear"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@linear/mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2) Automate UI workflows with Browser Automation (Alpha)
&lt;/h3&gt;

&lt;p&gt;When the UI is the source of truth (admin consoles, partner portals, legacy tools), MCCP Browser sessions let your assistant execute the workflow directly in a real browser session.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Stay safe with draft-first workflows
&lt;/h3&gt;

&lt;p&gt;For posts, announcements, and user-facing updates, the best default is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate content&lt;/li&gt;
&lt;li&gt;Save as &lt;strong&gt;draft&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Human review&lt;/li&gt;
&lt;li&gt;Publish explicitly&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Use cases (where readers will feel the value)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developer productivity:&lt;/strong&gt; pull context from docs/tickets via MCP, generate changelogs and release notes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content operations:&lt;/strong&gt; draft DEV.to/Hashnode posts from real product updates, keep them reviewable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ops &amp;amp; support:&lt;/strong&gt; create structured summaries and next steps from your tool stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Growth automation:&lt;/strong&gt; repeatable, safe workflows across platforms&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Keywords (optional, but good for SEO)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MCCP&lt;/li&gt;
&lt;li&gt;Managed Capabilities Control Plane&lt;/li&gt;
&lt;li&gt;MCP (Model Context Protocol)&lt;/li&gt;
&lt;li&gt;MCP server integrations&lt;/li&gt;
&lt;li&gt;browser automation for AI agents&lt;/li&gt;
&lt;li&gt;AI agent tools / agent ops&lt;/li&gt;
&lt;li&gt;developer automation platform&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Call to action
&lt;/h2&gt;

&lt;p&gt;If you’re exploring MCP + browser automation and want a managed way to ship it with safer defaults, start here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://clawship.app" rel="noopener noreferrer"&gt;https://clawship.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devtools</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Host Your Own Matrix + Element on a Server (Without Losing Your Weekend)</title>
      <dc:creator>El Housseine Jaafari</dc:creator>
      <pubDate>Sat, 21 Feb 2026 17:05:22 +0000</pubDate>
      <link>https://dev.to/jefferyhus/host-your-own-matrix-element-on-a-server-without-losing-your-weekend-559p</link>
      <guid>https://dev.to/jefferyhus/host-your-own-matrix-element-on-a-server-without-losing-your-weekend-559p</guid>
      <description>&lt;h2&gt;
  
  
  Why Matrix, why now?
&lt;/h2&gt;

&lt;p&gt;Matrix is an open protocol for real-time messaging. The two pieces you’ll run are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Synapse&lt;/strong&gt; (the Matrix homeserver): users, rooms, federation, auth, storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Element&lt;/strong&gt; (the client): the web app people actually use to chat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; absolutely self-host this. You &lt;em&gt;can&lt;/em&gt; also absolutely spend a weekend in TLS/DNS/Reverse-proxy purgatory if you’re not careful. Let’s do it the sane way.&lt;/p&gt;




&lt;h2&gt;
  
  
  What you’ll set up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A VPS/server with Linux&lt;/li&gt;
&lt;li&gt;A domain, with:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;matrix.example.com&lt;/code&gt; → Synapse&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;element.example.com&lt;/code&gt; → Element (web)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Nginx as reverse proxy&lt;/li&gt;

&lt;li&gt;Let’s Encrypt TLS certificates&lt;/li&gt;

&lt;li&gt;Synapse running in Docker&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This tutorial assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ubuntu 22.04+&lt;/li&gt;
&lt;li&gt;You have SSH access and a domain name&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 0: Pick your domain + DNS
&lt;/h2&gt;

&lt;p&gt;You need two DNS records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A/AAAA&lt;/strong&gt; record: &lt;code&gt;matrix.example.com&lt;/code&gt; → your server IP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A/AAAA&lt;/strong&gt; record: &lt;code&gt;element.example.com&lt;/code&gt; → your server IP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Give DNS a minute to propagate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Install prerequisites
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker.io docker-compose-plugin nginx certbot python3-certbot-nginx
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; docker nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Generate Synapse config (Docker)
&lt;/h2&gt;

&lt;p&gt;Create a working directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/matrix/synapse
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/matrix/synapse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate Synapse configuration (replace &lt;code&gt;example.com&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SYNAPSE_SERVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SYNAPSE_REPORT_STATS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="se"&gt;\&lt;/span&gt;
  matrixdotorg/synapse:latest generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates &lt;code&gt;homeserver.yaml&lt;/code&gt; and keys in &lt;code&gt;~/matrix/synapse&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Create a docker-compose.yml for Synapse
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;synapse&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;matrixdotorg/synapse:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;synapse&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data:/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYNAPSE_CONFIG_PATH=/data/homeserver.yaml&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8008:8008"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, move your generated config into &lt;code&gt;./data&lt;/code&gt; (Synapse expects &lt;code&gt;/data&lt;/code&gt; inside the container):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; data
&lt;span class="c"&gt;# If generate already wrote into ./data, skip this.&lt;/span&gt;
&lt;span class="c"&gt;# Otherwise, move the generated files:&lt;/span&gt;
&lt;span class="nb"&gt;shopt&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; dotglob
&lt;span class="nb"&gt;mv &lt;/span&gt;homeserver.yaml &lt;span class="k"&gt;*&lt;/span&gt;signing.key data/ 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start Synapse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Synapse is now listening on &lt;strong&gt;&lt;a href="http://127.0.0.1:8008" rel="noopener noreferrer"&gt;http://127.0.0.1:8008&lt;/a&gt;&lt;/strong&gt; (we’ll put Nginx in front).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Nginx reverse proxy for Synapse
&lt;/h2&gt;

&lt;p&gt;Create an Nginx site for &lt;code&gt;matrix.example.com&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/nginx/sites-available/matrix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;matrix.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:8008&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/matrix /etc/nginx/sites-enabled/matrix
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Add HTTPS (Let’s Encrypt)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; matrix.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow prompts. Certbot updates Nginx to serve HTTPS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Set up Element Web
&lt;/h2&gt;

&lt;p&gt;Element Web is just static files served by a web server. Easiest: run it in Docker and proxy it.&lt;/p&gt;

&lt;p&gt;Create a directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/matrix/element
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/matrix/element
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vectorim/element-web:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;element&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;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;8080:80"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./config.json:/app/config.json:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;config.json&lt;/code&gt; (replace domains):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"default_server_config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"m.homeserver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"base_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://matrix.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"server_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example.com"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"disable_custom_urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"disable_guests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"default_theme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"light"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start Element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Element is now at &lt;strong&gt;&lt;a href="http://127.0.0.1:8080" rel="noopener noreferrer"&gt;http://127.0.0.1:8080&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Nginx reverse proxy + HTTPS for Element
&lt;/h2&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/nginx/sites-available/element
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;element.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable + reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/element /etc/nginx/sites-enabled/element
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add HTTPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; element.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you should be able to open:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://element.example.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and point it at your homeserver.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Create your first Matrix user
&lt;/h2&gt;

&lt;p&gt;By default, Synapse disables open registration (good). Create an admin user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; synapse register_new_matrix_user &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; /data/homeserver.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:8008
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow prompts. You can create additional non-admin users the same way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 9 (Important): Don’t forget the boring-but-deadly parts
&lt;/h2&gt;

&lt;p&gt;Self-hosting isn’t hard, it’s just &lt;em&gt;annoying&lt;/em&gt; in exactly the places you can’t afford to mess up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backups (Synapse data + database if you add Postgres)&lt;/li&gt;
&lt;li&gt;Updates (Synapse and Element)&lt;/li&gt;
&lt;li&gt;Monitoring logs&lt;/li&gt;
&lt;li&gt;Firewalling and SSH hardening&lt;/li&gt;
&lt;li&gt;Rate-limits and anti-spam settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re doing this for a community, these are not optional.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to save yourself the headache?
&lt;/h2&gt;

&lt;p&gt;If you read the steps above and thought “I’d rather eat glass than debug Nginx + TLS at 2am,” here’s the spicy truth: &lt;strong&gt;you can skip most of this pain&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using &lt;strong&gt;&lt;a href="https://clawship.app" rel="noopener noreferrer"&gt;https://clawship.app&lt;/a&gt;&lt;/strong&gt;, you can spin up Matrix + Element fast and also get &lt;strong&gt;your own bot interaction&lt;/strong&gt; without babysitting the plumbing. Same end goal, less yak-shaving.&lt;/p&gt;

&lt;p&gt;(If you &lt;em&gt;like&lt;/em&gt; yak-shaving, carry on. No judgment. Mild concern, maybe.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Element loads but can’t connect&lt;/strong&gt;: confirm &lt;code&gt;config.json&lt;/code&gt; points to &lt;code&gt;https://matrix.example.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;502 from Nginx&lt;/strong&gt;: verify containers are running:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Certbot fails&lt;/strong&gt;: DNS record is wrong or not propagated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synapse feels slow&lt;/strong&gt;: consider switching to Postgres (recommended for real use).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;You now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A working Matrix homeserver (Synapse)&lt;/li&gt;
&lt;li&gt;Element Web served securely over HTTPS&lt;/li&gt;
&lt;li&gt;A foundation you can harden and scale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want, tell me your domain + whether you want Postgres and I’ll provide a production-grade compose setup (Synapse + Postgres + Redis, plus safer Nginx headers).&lt;/p&gt;

</description>
      <category>matrix</category>
      <category>selfhosted</category>
      <category>linux</category>
      <category>devops</category>
    </item>
    <item>
      <title>Self‑Hosting Matrix + Element on Your Own Server (The Friendly, Spicy Tutorial)</title>
      <dc:creator>El Housseine Jaafari</dc:creator>
      <pubDate>Sat, 21 Feb 2026 17:02:07 +0000</pubDate>
      <link>https://dev.to/jefferyhus/self-hosting-matrix-element-on-your-own-server-the-friendly-spicy-tutorial-5a22</link>
      <guid>https://dev.to/jefferyhus/self-hosting-matrix-element-on-your-own-server-the-friendly-spicy-tutorial-5a22</guid>
      <description>&lt;h2&gt;
  
  
  Why you’d do this (and why it’s a bit of a rite of passage)
&lt;/h2&gt;

&lt;p&gt;Matrix is an open, decentralized communication protocol. Element is the most common client for it. Self‑hosting them is appealing when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ownership over your comms stack&lt;/li&gt;
&lt;li&gt;Your own domain + branding&lt;/li&gt;
&lt;li&gt;Data residency / compliance control&lt;/li&gt;
&lt;li&gt;The satisfaction of running real infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s also… a small pile of moving parts: DNS, TLS, reverse proxies, federation ports, well‑known files, and making sure your homeserver doesn’t melt the moment you enable registration.&lt;/p&gt;

&lt;p&gt;This guide walks you through a &lt;strong&gt;practical, beginner‑friendly&lt;/strong&gt; setup using &lt;strong&gt;Docker Compose&lt;/strong&gt; and &lt;strong&gt;Synapse&lt;/strong&gt; (the reference Matrix homeserver).&lt;/p&gt;




&lt;h2&gt;
  
  
  What you’ll build
&lt;/h2&gt;

&lt;p&gt;By the end you’ll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Matrix homeserver at: &lt;code&gt;matrix.yourdomain.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Element Web at: &lt;code&gt;chat.yourdomain.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;HTTPS everywhere (Let’s Encrypt)&lt;/li&gt;
&lt;li&gt;A working client login and room chat&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Assumptions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You own a domain, e.g. &lt;code&gt;yourdomain.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You have a VPS/server (Ubuntu/Debian‑like)&lt;/li&gt;
&lt;li&gt;You can open ports &lt;strong&gt;80&lt;/strong&gt; and &lt;strong&gt;443&lt;/strong&gt; to the server&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 0: DNS (don’t skip this, future‑you will cry)
&lt;/h2&gt;

&lt;p&gt;Create DNS records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;matrix.yourdomain.com&lt;/code&gt; → your server IP (A/AAAA)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chat.yourdomain.com&lt;/code&gt; → your server IP (A/AAAA)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; put both behind the same reverse proxy; we’ll do that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Install Docker + Compose
&lt;/h2&gt;

&lt;p&gt;On most modern distros:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker.io docker-compose-plugin
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log out and back in so your user is in the &lt;code&gt;docker&lt;/code&gt; group.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Pick a reverse proxy (Caddy keeps your blood pressure lower)
&lt;/h2&gt;

&lt;p&gt;You can do this with Nginx + Certbot, Traefik, etc. For a clean tutorial, &lt;strong&gt;Caddy&lt;/strong&gt; is hard to beat: automatic TLS, simple config.&lt;/p&gt;

&lt;p&gt;We’ll run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;caddy&lt;/code&gt; (reverse proxy + TLS)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;synapse&lt;/code&gt; (Matrix homeserver)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;element-web&lt;/code&gt; (web client)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postgres&lt;/code&gt; (recommended DB for Synapse)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a working directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/matrix-stack
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/matrix-stack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Generate Synapse config
&lt;/h2&gt;

&lt;p&gt;Synapse needs a config file + keys.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;docker-compose.yml&lt;/code&gt; we’ll fill in (don’t run it yet):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;synapse&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CHANGE_ME_STRONG&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;synapse&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data/postgres:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;synapse&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;matrixdotorg/synapse:latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SYNAPSE_SERVER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yourdomain.com&lt;/span&gt;
      &lt;span class="na"&gt;SYNAPSE_REPORT_STATS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data/synapse:/data&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;element-web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vectorim/element-web:latest&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data/element/config.json:/app/config.json:ro&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;caddy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;caddy:2&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./Caddyfile:/etc/caddy/Caddyfile:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data/caddy:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data/caddy-config:/config&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now generate Synapse config and keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/data/synapse:/data"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SYNAPSE_SERVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yourdomain.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SYNAPSE_REPORT_STATS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="se"&gt;\&lt;/span&gt;
  matrixdotorg/synapse:latest generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates &lt;code&gt;data/synapse/homeserver.yaml&lt;/code&gt; and signing keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Synapse to use Postgres
&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;data/synapse/homeserver.yaml&lt;/code&gt; and set the database section (replace the default &lt;code&gt;sqlite3&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;psycopg2&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;synapse&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CHANGE_ME_STRONG"&lt;/span&gt;
    &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;synapse&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;cp_min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;cp_max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also set the public base URL:&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;public_baseurl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://matrix.yourdomain.com/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Configure Element Web
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;data/element/config.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"default_server_config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"m.homeserver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"base_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://matrix.yourdomain.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"server_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yourdomain.com"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"disable_custom_urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"disable_guests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Matrix"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Reverse proxy + TLS with Caddy
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;Caddyfile&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;matrix.yourdomain.com {
  reverse_proxy synapse:8008
}

chat.yourdomain.com {
  reverse_proxy element-web:80
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Synapse listens on &lt;strong&gt;8008&lt;/strong&gt; inside the container by default.&lt;/p&gt;

&lt;p&gt;Start everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check logs if needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6: Create your first user
&lt;/h2&gt;

&lt;p&gt;Exec into the synapse container and register a user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;synapse register_new_matrix_user &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; /data/homeserver.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:8008
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the prompts (username/password). Then open:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Element Web: &lt;code&gt;https://chat.yourdomain.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Log in with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;username: &lt;code&gt;@youruser:yourdomain.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 7 (important): Lock it down a bit
&lt;/h2&gt;

&lt;p&gt;Self‑hosting chat is fun until it becomes a spam magnet.&lt;/p&gt;

&lt;p&gt;Consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Disable open registration&lt;/strong&gt; unless you truly want it&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;reCAPTCHA&lt;/strong&gt; / token‑based registration if you must allow signups&lt;/li&gt;
&lt;li&gt;Keep Synapse updated&lt;/li&gt;
&lt;li&gt;Monitor disk space (media can grow fast)&lt;/li&gt;
&lt;li&gt;Back up Postgres + Synapse keys (&lt;code&gt;data/synapse&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In &lt;code&gt;homeserver.yaml&lt;/code&gt;, check:&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;enable_registration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Federation and “well‑known” (optional, but good to know)
&lt;/h2&gt;

&lt;p&gt;If you want &lt;code&gt;@user:yourdomain.com&lt;/code&gt; to work nicely and support federation cleanly, you’ll eventually run into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.well-known/matrix/server&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.well-known/matrix/client&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Potentially opening port &lt;strong&gt;8448&lt;/strong&gt; (depends on your setup)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where the “I just wanted to chat with friends” journey turns into “I am now operating a small internet service.” Congrats.&lt;/p&gt;

&lt;p&gt;If you want a follow‑up post, I can provide a clean, battle‑tested &lt;code&gt;.well-known&lt;/code&gt; + federation configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  The honest part: this is a headache you don’t have to keep
&lt;/h2&gt;

&lt;p&gt;If you love tinkering, self‑hosting Matrix is rewarding.&lt;/p&gt;

&lt;p&gt;If you’d rather skip the yak‑shaving and still get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matrix + Element on your own domain&lt;/li&gt;
&lt;li&gt;A managed path to add a bot and interact with it&lt;/li&gt;
&lt;li&gt;A smoother “it just works” experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…you can save yourself the whole weekend by using &lt;strong&gt;&lt;a href="https://clawship.app" rel="noopener noreferrer"&gt;https://clawship.app&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Clawship is built to help you spin up your stack faster and add &lt;strong&gt;your own bot interaction&lt;/strong&gt; without reinventing the infrastructure wheel. You focus on the community and automation, not the reverse proxy sudoku.&lt;/p&gt;




&lt;h2&gt;
  
  
  Troubleshooting quick hits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caddy won’t get certificates&lt;/strong&gt; → check DNS points to the server and ports 80/443 are open&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synapse errors on DB&lt;/strong&gt; → verify Postgres credentials match in &lt;code&gt;homeserver.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Element loads but can’t login&lt;/strong&gt; → confirm Element config points at &lt;code&gt;https://matrix.yourdomain.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media storage exploding&lt;/strong&gt; → set retention policies and monitor disk usage&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What’s next
&lt;/h2&gt;

&lt;p&gt;If you want, I can write a follow‑up tutorial on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Federation + &lt;code&gt;.well-known&lt;/code&gt; done correctly&lt;/li&gt;
&lt;li&gt;Adding a Matrix bot (and safe bot permissions)&lt;/li&gt;
&lt;li&gt;Moderation + anti‑spam strategy&lt;/li&gt;
&lt;li&gt;Backups and disaster recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if you’d rather just ship: &lt;strong&gt;&lt;a href="https://clawship.app" rel="noopener noreferrer"&gt;https://clawship.app&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>matrix</category>
      <category>selfhosted</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>From Prompt to Post: Secure Auto‑Publishing to DEV.to (and Medium) with Clawship</title>
      <dc:creator>El Housseine Jaafari</dc:creator>
      <pubDate>Sat, 21 Feb 2026 02:48:00 +0000</pubDate>
      <link>https://dev.to/jefferyhus/from-prompt-to-post-secure-auto-publishing-to-devto-and-medium-with-clawship-2a7j</link>
      <guid>https://dev.to/jefferyhus/from-prompt-to-post-secure-auto-publishing-to-devto-and-medium-with-clawship-2a7j</guid>
      <description>&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Writing is one of the highest‑leverage ways to ship ideas: announcements, changelogs, tutorials, and design notes. The problem is the last mile—formatting, previews, tags, canonical URLs, and the “did it actually publish?” dance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clawship.app&lt;/strong&gt; aims to make that last mile boring (in a good way): you write the content, your assistant turns it into a clean markdown article, and Clawship can &lt;strong&gt;save as a draft or publish&lt;/strong&gt; to &lt;strong&gt;DEV.to&lt;/strong&gt; and &lt;strong&gt;Medium&lt;/strong&gt; through your connected accounts.&lt;/p&gt;

&lt;p&gt;This post explains the workflow, the prototype value, and the security model at a practical level.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Clawship gives you
&lt;/h2&gt;

&lt;p&gt;At a high level, Clawship combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A hosted assistant you can chat with to &lt;strong&gt;draft, edit, and polish&lt;/strong&gt; posts.&lt;/li&gt;
&lt;li&gt;A publishing bridge that can &lt;strong&gt;create drafts&lt;/strong&gt; or &lt;strong&gt;publish&lt;/strong&gt; to:

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DEV.to&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Medium&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;A workflow that keeps you in control: you can require “draft only,” request a review, then publish when ready.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The result: fewer tools, fewer copy/paste errors, and a repeatable publishing pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  The end‑to‑end workflow (prototype‑friendly)
&lt;/h2&gt;

&lt;p&gt;Here’s the simplest “prove it works” flow your team can demo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask for an article&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Example: “Write a DEV.to article about our feature X, 900–1200 words, include code samples.”&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterate in chat&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Tighten the outline&lt;/li&gt;
&lt;li&gt;Add screenshots/links&lt;/li&gt;
&lt;li&gt;Adjust tone (marketing vs. technical)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate publish‑ready markdown&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Proper headings, lists, code fences, etc.&lt;/li&gt;
&lt;li&gt;A clear structure (intro → problem → solution → steps → FAQ)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish as a draft (recommended)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Clawship creates a DEV.to draft under your connected account.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review + publish&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;You confirm formatting, tags, canonical URL, and call‑to‑action.&lt;/li&gt;
&lt;li&gt;Then publish to DEV.to and optionally mirror to Medium.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even with a prototype, this flow demonstrates real user value: the assistant produces content in the exact format platforms expect, and the “last mile” becomes one click.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the assistant produces (DEV.to‑ready)
&lt;/h2&gt;

&lt;p&gt;DEV.to is markdown‑first. A Clawship‑generated post can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;##&lt;/code&gt; and &lt;code&gt;###&lt;/code&gt; headings with consistent hierarchy&lt;/li&gt;
&lt;li&gt;Bullet lists for scannability&lt;/li&gt;
&lt;li&gt;Code blocks with language hints&lt;/li&gt;
&lt;li&gt;Short, skimmable paragraphs (DEV.to readers love this)&lt;/li&gt;
&lt;li&gt;A tight conclusion + CTA back to &lt;strong&gt;&lt;a href="https://clawship.app" rel="noopener noreferrer"&gt;https://clawship.app&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; If you want maximum credibility in a demo, ask the assistant to include a “What I built / How it works / What’s next” section and keep claims testable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Draft vs. publish: staying in control
&lt;/h2&gt;

&lt;p&gt;A secure publishing workflow isn’t just about protecting accounts—it’s also about preventing accidental public posts.&lt;/p&gt;

&lt;p&gt;Clawship supports a &lt;strong&gt;two‑step posture&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default: Draft mode&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;The assistant writes the article and saves it as a draft.&lt;/li&gt;
&lt;li&gt;You review on DEV.to/Medium before publishing.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Publish mode (explicit)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;You must clearly ask to publish.&lt;/li&gt;
&lt;li&gt;This reduces “oops” moments and helps teams keep approvals in the loop.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;For prototypes and early teams, &lt;strong&gt;draft‑first&lt;/strong&gt; is the safest and most demo‑friendly default.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security model (what “secure” means in practice)
&lt;/h2&gt;

&lt;p&gt;When you’re proving a prototype, “secure” should mean concrete, observable properties:&lt;/p&gt;

&lt;h3&gt;
  
  
  1) You control where content goes
&lt;/h3&gt;

&lt;p&gt;Publishing only happens to the platforms you connect (DEV.to and/or Medium). If you don’t connect an account, there’s nothing to publish to.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Least‑surprise behavior
&lt;/h3&gt;

&lt;p&gt;Good assistants should not take irreversible actions without an explicit request. A robust posture is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate content → &lt;strong&gt;create draft&lt;/strong&gt; → wait for confirmation → publish&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3) Scope‑limited permissions (platform‑dependent)
&lt;/h3&gt;

&lt;p&gt;DEV.to and Medium integrations typically rely on an API token or OAuth‑style authorization. The safest approach is to grant only what’s needed to create or update posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  4) No “mystery steps”
&lt;/h3&gt;

&lt;p&gt;A publishing pipeline should be auditable at the product level:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What platform is being targeted?&lt;/li&gt;
&lt;li&gt;Is the action “draft” or “publish”?&lt;/li&gt;
&lt;li&gt;What title/tags are set?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clawship’s goal is to make these knobs obvious so teams can verify behavior during demos and tighten it later for production.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(If you’re doing an external security review, treat tokens like passwords: rotate them, limit access, and store them using your platform’s secret management.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Medium support (same idea, different audience)
&lt;/h2&gt;

&lt;p&gt;Once you have the markdown and a clear structure, Medium becomes a second distribution channel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DEV.to tends to reward practical, tool‑driven posts.&lt;/li&gt;
&lt;li&gt;Medium tends to reward narrative + perspective.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Clawship you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write once&lt;/li&gt;
&lt;li&gt;Adapt the intro/outro for the audience&lt;/li&gt;
&lt;li&gt;Save drafts in both places&lt;/li&gt;
&lt;li&gt;Publish when the timing is right&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A concrete demo script (copy/paste)
&lt;/h2&gt;

&lt;p&gt;If you want to demonstrate the prototype end‑to‑end in under 5 minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In chat: “Write a DEV.to article: &lt;em&gt;From Prompt to Post with Clawship&lt;/em&gt;, include a 5‑step workflow and a security section. End with a CTA to clawship.app.”&lt;/li&gt;
&lt;li&gt;Ask: “Now tighten it to ~900 words and make the tone more technical.”&lt;/li&gt;
&lt;li&gt;Ask: “Create a DEV.to draft with tags: &lt;code&gt;productivity&lt;/code&gt;, &lt;code&gt;devtools&lt;/code&gt;, &lt;code&gt;writing&lt;/code&gt;, &lt;code&gt;ai&lt;/code&gt;.”&lt;/li&gt;
&lt;li&gt;Open the DEV.to draft, show formatting is correct.&lt;/li&gt;
&lt;li&gt;Publish (optional) or keep it as draft to emphasize safe defaults.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Call to action
&lt;/h2&gt;

&lt;p&gt;If you want writing + publishing to feel like part of your developer workflow—not a separate chore—try &lt;strong&gt;Clawship&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;strong&gt;&lt;a href="https://clawship.app" rel="noopener noreferrer"&gt;https://clawship.app&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Use it to draft technical posts, product updates, or docs&lt;/li&gt;
&lt;li&gt;Publish to &lt;strong&gt;DEV.to&lt;/strong&gt; and &lt;strong&gt;Medium&lt;/strong&gt; with a draft‑first workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re evaluating the prototype, the best test is simple: pick a real post your team needs to publish this week and see how much time you save.&lt;/p&gt;

</description>
      <category>devtools</category>
      <category>productivity</category>
      <category>writing</category>
      <category>ai</category>
    </item>
    <item>
      <title>Fixing no space left on device for Docker when /run is full (Ubuntu)</title>
      <dc:creator>El Housseine Jaafari</dc:creator>
      <pubDate>Tue, 07 Oct 2025 11:45:38 +0000</pubDate>
      <link>https://dev.to/jefferyhus/fixing-no-space-left-on-device-for-docker-when-run-is-full-ubuntu-1n0c</link>
      <guid>https://dev.to/jefferyhus/fixing-no-space-left-on-device-for-docker-when-run-is-full-ubuntu-1n0c</guid>
      <description>&lt;p&gt;If Docker or containerd suddenly refuse to start containers with errors like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error response from daemon: failed to create task for container:
failed to start shim: symlink ... /run/containerd/...: no space left on device: unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…but &lt;code&gt;df -h&lt;/code&gt; shows plenty of free disk on &lt;code&gt;/&lt;/code&gt;, you’re likely hitting a &lt;strong&gt;full &lt;code&gt;/run&lt;/code&gt; (tmpfs)&lt;/strong&gt; or &lt;strong&gt;inode exhaustion&lt;/strong&gt;. This guide explains why it happens and how to fix it quickly—and permanently.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR (Quick Unblock)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Give /run more room (temporary until reboot)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-o&lt;/span&gt; remount,size&lt;span class="o"&gt;=&lt;/span&gt;512M /run   &lt;span class="c"&gt;# or size=1G if you have RAM&lt;/span&gt;

&lt;span class="c"&gt;# Restart services to clear stale state&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart systemd-udevd
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart containerd docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it still fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop docker containerd
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /run/containerd/io.containerd.runtime.v2.task/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start containerd docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it persistent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'tmpfs /run tmpfs defaults,size=512M 0 0'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/fstab
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-o&lt;/span&gt; remount /run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/run&lt;/code&gt; is a RAM-backed tmpfs&lt;/strong&gt; used by systemd, udev, Docker/containerd shims, etc.&lt;/li&gt;
&lt;li&gt;On some systems, &lt;code&gt;/run&lt;/code&gt; defaults to a &lt;strong&gt;small size&lt;/strong&gt; (e.g., ~200 MB).&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;/run&lt;/code&gt; runs out of &lt;strong&gt;space or inodes&lt;/strong&gt;, containerd can’t create its shim files/symlinks under &lt;code&gt;/run/containerd/...&lt;/code&gt;, resulting in &lt;strong&gt;“no space left on device”&lt;/strong&gt; even though your disk has free space.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How to confirm the root cause
&lt;/h2&gt;

&lt;p&gt;Check Docker’s data roots and the &lt;code&gt;/run&lt;/code&gt; mount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker info 2&amp;gt;/dev/null | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'s/^ Docker Root Dir: //p'&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-hT&lt;/span&gt; /var/lib/docker /var/lib/containerd /run
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt;  /var/lib/docker /var/lib/containerd /run
&lt;span class="nb"&gt;sudo du&lt;/span&gt; &lt;span class="nt"&gt;-xhd1&lt;/span&gt; /run | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What to look for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/run&lt;/code&gt; type is &lt;code&gt;tmpfs&lt;/code&gt; and &lt;strong&gt;Use% is ~100%&lt;/strong&gt;; or&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/run&lt;/code&gt; &lt;strong&gt;inodes are 100% used&lt;/strong&gt; (&lt;code&gt;df -i&lt;/code&gt; shows &lt;code&gt;IUse% 100%&lt;/code&gt;); and&lt;/li&gt;
&lt;li&gt;Large directories under &lt;code&gt;/run&lt;/code&gt; (commonly &lt;code&gt;udev&lt;/code&gt;, &lt;code&gt;containerd&lt;/code&gt;, or &lt;code&gt;docker&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example output (problem case):&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;tmpfs  197M  197M  8.0K  100%  /run
tmpfs  251822 251816 6  100%   /run
sudo du -xhd1 /run | sort -h
...
3.7M  /run/containerd
193M  /run/udev
197M  /run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step-by-step fixes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Temporarily grow &lt;code&gt;/run&lt;/code&gt; (immediate relief)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-o&lt;/span&gt; remount,size&lt;span class="o"&gt;=&lt;/span&gt;512M /run      &lt;span class="c"&gt;# or size=1G on bigger hosts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restart the relevant services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart systemd-udevd
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart containerd docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-hT&lt;/span&gt; /run &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; /run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2) Clear stale containerd runtime state (if needed)
&lt;/h3&gt;

&lt;p&gt;Stop services, remove leftover shim dirs, start again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop docker containerd
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /run/containerd/io.containerd.runtime.v2.task/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start containerd docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Safe because those are &lt;strong&gt;runtime&lt;/strong&gt; artifacts recreated on start.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3) Make the larger &lt;code&gt;/run&lt;/code&gt; persistent (survives reboot)
&lt;/h3&gt;

&lt;p&gt;Append to &lt;code&gt;/etc/fstab&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'tmpfs /run tmpfs defaults,size=512M 0 0'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/fstab
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-o&lt;/span&gt; remount /run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick a size appropriate to your RAM (e.g., 512 MB–1 GB for servers running containers).&lt;/p&gt;




&lt;h2&gt;
  
  
  Related issues &amp;amp; checks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A) Inode exhaustion (lots of tiny files)
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;df -i /run&lt;/code&gt; shows 100% usage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The remount with a larger size also increases available inodes on tmpfs.&lt;/li&gt;
&lt;li&gt;Restarting &lt;code&gt;systemd-udevd&lt;/code&gt;, &lt;code&gt;containerd&lt;/code&gt;, and &lt;code&gt;docker&lt;/code&gt; frees stale runtime entries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Docker’s persistent storage (not &lt;code&gt;/run&lt;/code&gt;), prune if inodes are tight:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker system &lt;span class="nb"&gt;df
&lt;/span&gt;docker system prune &lt;span class="nt"&gt;-f&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt;
docker volume prune &lt;span class="nt"&gt;-f&lt;/span&gt;
docker builder prune &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  B) Docker root on a different filesystem that’s full
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;/var/lib/docker&lt;/code&gt; or &lt;code&gt;/var/lib/containerd&lt;/code&gt; are on a small/near-full partition, move Docker’s data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop docker containerd
&lt;span class="nb"&gt;sudo &lt;/span&gt;rsync &lt;span class="nt"&gt;-aHAXx&lt;/span&gt; &lt;span class="nt"&gt;--info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;progress2 /var/lib/docker/ /mnt/docker/
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'{\n  "data-root": "/mnt/docker"\n}\n'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/docker/daemon.json
&lt;span class="nb"&gt;sudo mv&lt;/span&gt; /var/lib/docker /var/lib/docker.bak
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /var/lib/docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;--bind&lt;/span&gt; /mnt/docker /var/lib/docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start containerd docker
docker info | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Docker Root Dir"&lt;/span&gt;
&lt;span class="c"&gt;# If all good, clean up:&lt;/span&gt;
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/docker.bak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Prevention tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Right-size &lt;code&gt;/run&lt;/code&gt;:&lt;/strong&gt; Set &lt;code&gt;size=512M&lt;/code&gt;–&lt;code&gt;1G&lt;/code&gt; in &lt;code&gt;/etc/fstab&lt;/code&gt; for hosts that run containers or have busy udev activity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prune regularly:&lt;/strong&gt; Set up periodic pruning for dangling images/builders/volumes if CI/build-heavy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor &lt;code&gt;/run&lt;/code&gt;:&lt;/strong&gt; Watch &lt;code&gt;Use%&lt;/code&gt; and inode use with a simple cron or systemd timer:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /run&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; /run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Avoid runaway logs/artifacts in &lt;code&gt;/run&lt;/code&gt;:&lt;/strong&gt; Runtime data should be short-lived; if something keeps filling &lt;code&gt;/run&lt;/code&gt;, investigate that service.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: I have free disk space. Why “no space left on device”?&lt;/strong&gt;&lt;br&gt;
Because the error refers to the &lt;strong&gt;&lt;code&gt;/run&lt;/code&gt; tmpfs&lt;/strong&gt;, not your disk. &lt;code&gt;/run&lt;/code&gt; can be full (or out of inodes) while &lt;code&gt;/&lt;/code&gt; has gigabytes free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Is it safe to delete things under &lt;code&gt;/run/containerd/...&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
Yes—&lt;strong&gt;only after stopping containerd and Docker&lt;/strong&gt;. Those are runtime artifacts and will be recreated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can I just reboot?&lt;/strong&gt;&lt;br&gt;
Often yes, but the problem will likely return unless you &lt;strong&gt;increase &lt;code&gt;/run&lt;/code&gt;&lt;/strong&gt; or fix the service causing the pressure.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;That’s it!&lt;/strong&gt; With a right-sized &lt;code&gt;/run&lt;/code&gt; and a quick service restart, Docker will stop tripping over “no space left on device” despite plenty of disk space.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>linux</category>
      <category>devops</category>
    </item>
    <item>
      <title>Implementing your own Proof Of Authentication. (Part 1)</title>
      <dc:creator>El Housseine Jaafari</dc:creator>
      <pubDate>Mon, 16 Dec 2024 17:56:07 +0000</pubDate>
      <link>https://dev.to/jefferyhus/implementing-your-own-proof-of-authentication-part-1-adb</link>
      <guid>https://dev.to/jefferyhus/implementing-your-own-proof-of-authentication-part-1-adb</guid>
      <description>&lt;p&gt;In the world of IoT, companies and innovators often turn to blockchain solutions to securely automate tasks such as collecting, filtering, and identifying incoming data. While decentralized networks can be quite helpful in many respects, certain bottlenecks still hinder scalability. For example, some consensus mechanisms require extensive re-validation and computational effort, increasing both the time and energy consumed by the network. This heightened resource usage is particularly undesirable for factories and other industries that aim to reduce their environmental impact, making the exploration of more efficient blockchain protocols essential.&lt;/p&gt;

&lt;p&gt;Thus comes the new consensus algorithm called Proof Of Authentication that will replace the existing one Proof of Work and introduce authentication in such environments to make the blockchain application-specific.&lt;/p&gt;

&lt;p&gt;The algorithm is designed to ensure secure and authenticated block validation in a blockchain network. Each node in the network begins by combining transactions into a block, which is then signed using the node's private key. This signed block is broadcasted across the network. Trusted nodes verify the block's signature using the source node's public key to confirm its authenticity. If the block is authenticated, it is appended with a cryptographic Proof of Authentication (PoAh), ensuring integrity and traceability, and then added to the blockchain. Blocks that fail authentication are discarded, maintaining the network's reliability and security. PoAh emphasizes a lightweight yet robust validation process, making it ideal for systems prioritizing transparency, trust, and resource efficiency.&lt;/p&gt;

&lt;p&gt;To simplify how the algorithm works, let us translate the theoretical concept into a practical Python example. The code below demonstrates the full process, from creating a block of transactions to signing, verifying, and appending it to the blockchain. By following this example, you can gain a clear understanding of the algorithm's steps and their implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;rsa&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sha256_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Generate keys for demonstration
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pubkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;privkey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rsa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newkeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# PoAh Procedure
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;poah_procedure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Step 1: Combine transactions to form a block
&lt;/span&gt;    &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 2: Sign the block with private key
&lt;/span&gt;    &lt;span class="n"&gt;signed_block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rsa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;privkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SHA-256&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 3: Broadcast (simulation)
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Broadcasting signed block...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 4: Trusted node verifies signature
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rsa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;signed_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pubkey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Block authenticated!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Step 5: Append PoAh and add to blockchain
&lt;/span&gt;        &lt;span class="n"&gt;poah&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Proof of Authentication&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;blockchain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; || &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;poah&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Block added to blockchain.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;rsa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VerificationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Block verification failed. Dropping block.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Blockchain simulation
&lt;/span&gt;&lt;span class="n"&gt;blockchain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="c1"&gt;# Example usage
&lt;/span&gt;&lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tx1: A-&amp;gt;B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tx2: C-&amp;gt;D&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;poah_procedure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Blockchain:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blockchain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>substrate</category>
      <category>blockchain</category>
      <category>rust</category>
      <category>iot</category>
    </item>
    <item>
      <title>You are the bottleneck</title>
      <dc:creator>El Housseine Jaafari</dc:creator>
      <pubDate>Mon, 26 Feb 2024 03:49:00 +0000</pubDate>
      <link>https://dev.to/jefferyhus/you-are-the-bottleneck-26fa</link>
      <guid>https://dev.to/jefferyhus/you-are-the-bottleneck-26fa</guid>
      <description>&lt;p&gt;In the spring of 2012, fresh out of the Technology Institute, I brimmed with ambition and an unyielding thirst for knowledge in the realm of technology. With grand aspirations of becoming one of the foremost software engineers, I set sail into the turbulent seas of the tech industry.&lt;/p&gt;

&lt;p&gt;My journey was marked by an unwavering dedication to learning, an unquenchable hunger to master new tools and languages. I immersed myself in the intricacies of coding patterns and algorithms, relentlessly seeking out solutions to every problem that crossed my path. Doubt never clouded my mind; I held firm to the belief that every challenge bore a solution within its depths.&lt;/p&gt;

&lt;p&gt;This relentless pursuit of excellence forged within me the essence of a perfectionist. Armed with mastery over the tools at my disposal, I refined my craft to the point where I wielded code like a virtuoso, crafting elegant solutions with the utmost efficiency.&lt;/p&gt;

&lt;p&gt;But time marched on, and with each passing year, the hunger within me grew. A voracious beast, it drove me to incessantly refine and perfect, never allowing me to rest until every line of code was pristine, every algorithm optimized to perfection.&lt;/p&gt;

&lt;p&gt;Then came the fateful year of 2018, a year of reckoning. Despite my expertise, a project of my own creation faltered in the face of customer expectations. In my pride, I cast blame upon those I was meant to serve, blind to the flaws in my own approach.&lt;/p&gt;

&lt;p&gt;On February 20th, 2018, the CEO implored me to "trust your expertise," entrusting me with the task of assembling a new team to salvage our project. With the weight of expectations upon my shoulders, I ventured forth, drawing upon years of experience to hand-pick a team worthy of the challenge.&lt;/p&gt;

&lt;p&gt;Four months later, we emerged victorious, our project launched and ready for the world. Yet, amidst the celebrations, a shadow loomed over my leadership. Though camaraderie thrived within the team, my relentless pursuit of perfection often hindered our progress.&lt;/p&gt;

&lt;p&gt;"You are the bottleneck," the CEO's words pierced through my defences, shattering the illusion of infallibility I had crafted around myself. At that moment of vulnerability, I realized the folly of my ways.&lt;/p&gt;

&lt;p&gt;With humility, I sought guidance, a departure from the solitary path I had trod for so long. Through books, articles, and podcasts, I embarked on a journey of self-discovery, striving to understand the intricacies of business and the art of customer satisfaction.&lt;/p&gt;

&lt;p&gt;And so, my journey continues, guided by the realization that true success lies not in the perfection of code, but in the fulfilment of customer needs. In this ever-evolving landscape of technology, I embrace the challenges that lie ahead, armed not just with technical prowess, but with a newfound understanding of the human element that underpins it all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Word Definitions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Brimmed&lt;/strong&gt;: Overflowed or filled to the edge; abounded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unquenchable&lt;/strong&gt;: Impossible to satisfy or extinguish.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intricacies&lt;/strong&gt;: Complex details or elements; the fine points.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Virtuoso&lt;/strong&gt;: A person highly skilled in a particular art or field, especially in music or the fine arts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implored&lt;/strong&gt;: Urgently requested or begged for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ventured&lt;/strong&gt;: Undertook a risky or daring journey or course of action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loomed&lt;/strong&gt;: Appeared as a threat or imminent danger; overshadowed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trod&lt;/strong&gt;: Walked or stepped on; followed a particular path or course of action.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>productivity</category>
      <category>career</category>
      <category>learning</category>
    </item>
    <item>
      <title>GraphQL Pagination</title>
      <dc:creator>El Housseine Jaafari</dc:creator>
      <pubDate>Tue, 18 Feb 2020 09:04:27 +0000</pubDate>
      <link>https://dev.to/jefferyhus/graphql-pagination-5d5b</link>
      <guid>https://dev.to/jefferyhus/graphql-pagination-5d5b</guid>
      <description>&lt;p&gt;Hey there! Glad you are here and welcome to &lt;code&gt;GraphQL Pagination&lt;/code&gt; tutorial. The goal of this tutorial is to guide you through setting up your pagination using &lt;code&gt;graphql&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;graphql&lt;/code&gt; there are two major approaches, the first one is &lt;code&gt;offset style&lt;/code&gt; and the second one goes by &lt;code&gt;cursor style&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As a &lt;strong&gt;Ninja&lt;/strong&gt; once said "行きましょ" 🙈.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offset Pagination
&lt;/h2&gt;

&lt;p&gt;Now that you have your blog up and running, you notice that you are querying all your posts. This is not efficient, especially if you go over a 100 blog posts. Your SQL query will grow by time and spend then more time to execute.&lt;/p&gt;

&lt;p&gt;To solve the problem, by default as someone who knows SQL well, you will probably think of pagination. The way you do it is by adding two arguments to your SQL query &lt;code&gt;offset&lt;/code&gt;&lt;sup&gt;1&lt;/sup&gt; and &lt;code&gt;limit&lt;/code&gt;&lt;sup&gt;2&lt;/sup&gt;. You are asking right now about how to achieve this in your &lt;code&gt;graphql&lt;/code&gt; server.&lt;/p&gt;

&lt;p&gt;Allow me to show you an example of fetching 10 blog posts from all your posts starting from the 11th one. Your query will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Until now everything seems to be working, this type of pagination works great if you have either a static or small data. It results into a good user experience for quickly fetching the next page data.&lt;/p&gt;

&lt;p&gt;But, this approach have its downsides and issues that needs to be addressed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;As mentioned before, in small datasets, the offset approach is good for you and will not break the user experience.&lt;/p&gt;

&lt;p&gt;But as the data grows in your dataset and you are trying to fetch the farthest of the results 😩 it will take longer than you can imagine (You may 😢).&lt;/p&gt;

&lt;p&gt;But how? (you ask). Let's break it down easily, take this query as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query is fetching 10 posts with an offset of 10K, in a first glance you think that's it, your database engine will directly understand the offset and start querying from that row.&lt;/p&gt;

&lt;p&gt;Well! That won't happen because the way a &lt;code&gt;relational&lt;/code&gt; database engine works, is that it will still have to compute all the skipped rows inside the server; therefore a large OFFSET might be inefficient.&lt;/p&gt;

&lt;p&gt;You see?! It's bad right, just like if you want to start reading a book from a certain page but you still have to go over every page and count it yourself without the use of an index.&lt;/p&gt;

&lt;p&gt;Don't worry, we got your back with a better solution 😎&lt;/p&gt;

&lt;h1&gt;
  
  
  Cursor pagination
&lt;/h1&gt;

&lt;p&gt;This approach tend to set a pointer to a specific record in your datatable. For it to work at its finest, the cursor must be unique and sequential. What this does, is that you will always fetch data after a certain row instead of relying in the positioning of rows inside your table.&lt;/p&gt;

&lt;p&gt;Let's have an example to understand this more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publisherId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query will skip all the 24th rows and returns the next 10 results in your dataset after the value of your &lt;code&gt;cursor&lt;/code&gt;. &lt;strong&gt;The &lt;code&gt;cursor&lt;/code&gt; here refers to the &lt;code&gt;id&lt;/code&gt; of our posts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Looking at your resolver, it will look like this:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S: We are using Sequelize as our ORM framework.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publisherId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;publisherId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;limit&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor based pagination is more performant than offsets because we can leverage database indexes on the columns in the where clause that are being used as cursors.&lt;/p&gt;

&lt;p&gt;There is more to this solution, and many libraries and frameworks out there are presenting you with their own cursor style pagination; But we can't go over each of if, we will stick to the one that is most used with &lt;code&gt;React&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relay style pagination
&lt;/h3&gt;

&lt;p&gt;Relay is a framework for building data driven React apps. Relay provides a standard mechanism to slice data and paginate the results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cursor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;edges&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;cursor&lt;/span&gt;
      &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;
        &lt;span class="nx"&gt;title&lt;/span&gt;
        &lt;span class="nx"&gt;content&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;pageInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;hasNextPage&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, slicing is done using the first argument. This is similar to the limit which we had applied in the earlier queries. Pagination is done using the after argument, which will pass a cursor expecting results to be after that cursor. It also asks for hasNextPage which tells the client whether there are more results, since there is no concept of total number of pages in Cursor pagination.&lt;/p&gt;

&lt;h1&gt;
  
  
  Recap
&lt;/h1&gt;

&lt;p&gt;We learned two different pagination styles, and walked trough both of it. To recap our tutorial, let's list again what each approach offers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offset
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It gives you the ability to see the total number of pages and their progress through that total.&lt;/li&gt;
&lt;li&gt;It gives you the ability to jump to a specific page within the set.&lt;/li&gt;
&lt;li&gt;It’s easy to implement as long as there is an explicit ordering of the results from a query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using LIMIT  OFFSET  doesn’t scale well for large datasets. As the offset increases the farther you go within the dataset, the database still has to read up to offset + count rows from disk, before discarding the offset and only returning count rows.&lt;/li&gt;
&lt;li&gt;If items are being written to the dataset at a high frequency, the page window becomes unreliable, potentially skipping or returning duplicate results.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cursor
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This will scale well for large datasets. We’re using a WHERE clause to fetch rows with &lt;code&gt;index&lt;/code&gt; values less than the last &lt;code&gt;index&lt;/code&gt; from the previous page. This lets us leverage the index on the column and the database doesn’t have to read any rows that we’ve already seen. We’re also not returning the total number of pages or items in the set, so we avoid having to calculate the full result set on each request.&lt;/li&gt;
&lt;li&gt;The pagination process is stabilized. Instead of calculating from scratch on each request based on the total number of items, we’re always fetching the next count rows after a specific reference point. If items are being written to the dataset at a high frequency, the overall position of the cursor in the set might change, but the pagination window adjusts accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cursor must be based on a unique, sequential column (or columns) in the source table.&lt;/li&gt;
&lt;li&gt;There is no concept of the total number of pages or results in the set.&lt;/li&gt;
&lt;li&gt;The client can’t jump to a specific page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;P.S.: This image shows the differences between both approaches.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Dictionary
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;(1): OFFSET says to skip that many rows before beginning to return rows.&lt;/li&gt;
&lt;li&gt;(2): LIMIT is an optional clause of the SELECT statement that returns a subset of rows returned by the query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that said, I hope you enjoyed reading the article and understood the differences between both approaches 😄&lt;/p&gt;

&lt;p&gt;Brought to you with ❤️&lt;/p&gt;

&lt;p&gt;=====================&lt;/p&gt;

&lt;p&gt;Github repo: &lt;a href="https://github.com/JefferyHus/graphql-pagination" rel="noopener noreferrer"&gt;https://github.com/JefferyHus/graphql-pagination&lt;/a&gt;&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>pagination</category>
      <category>node</category>
      <category>apollo</category>
    </item>
  </channel>
</rss>
