<?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: FlatlinerAtheist | CLI-NERDS</title>
    <description>The latest articles on DEV Community by FlatlinerAtheist | CLI-NERDS (@clinerds).</description>
    <link>https://dev.to/clinerds</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4189900%2F91917414-3cb4-4a8a-9a53-8c115a821b0b.png</url>
      <title>DEV Community: FlatlinerAtheist | CLI-NERDS</title>
      <link>https://dev.to/clinerds</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/clinerds"/>
    <language>en</language>
    <item>
      <title>SQLite in Production: A Practical Setup with Litestream Backups</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:10:13 +0000</pubDate>
      <link>https://dev.to/clinerds/sqlite-in-production-a-practical-setup-with-litestream-backups-2kap</link>
      <guid>https://dev.to/clinerds/sqlite-in-production-a-practical-setup-with-litestream-backups-2kap</guid>
      <description>&lt;p&gt;SQLite often gets dismissed as a toy database, which surprises the many useful applications quietly running on it. For a small service with one application server, it can remove an entire database process, its network configuration, and a surprising amount of operational work.&lt;/p&gt;

&lt;p&gt;The catch is not whether SQLite can store production data. It can. The catch is whether your access pattern fits and whether you can restore it after a bad day. &lt;strong&gt;Litestream&lt;/strong&gt; helps with the second part by continuously replicating SQLite changes to object storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  When SQLite is a sensible production choice
&lt;/h2&gt;

&lt;p&gt;SQLite runs inside your application process and stores the database on local disk. Reads do not cross a network, and writes use ACID transactions. This is excellent for blogs, small SaaS tools, internal apps, and single-node self-hosted services.&lt;/p&gt;

&lt;p&gt;It is less suitable when many application servers must write concurrently, you need horizontal write scaling, or clients require direct network access to the database. PostgreSQL remains the better general answer for those cases. Our &lt;a href="https://clinerds.com/article/mysql-vs-postgresql-2026" rel="noopener noreferrer"&gt;MySQL vs PostgreSQL comparison&lt;/a&gt; covers the server-database side of that decision.&lt;/p&gt;

&lt;p&gt;Choose SQLite because the architecture is genuinely small, not because avoiding PostgreSQL is a goal in itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure it like you mean it
&lt;/h2&gt;

&lt;p&gt;Enable write-ahead logging and a busy timeout when your application opens the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;journal_mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;busy_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;foreign_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WAL allows readers and a writer to work more comfortably together. A busy timeout tells SQLite to wait briefly when the database is locked instead of immediately failing. Foreign-key enforcement should be enabled per connection because SQLite does not assume you want it.&lt;/p&gt;

&lt;p&gt;Keep the database on a local disk. A network filesystem with weak locking or unreliable flush behavior can corrupt the assumptions SQLite depends on. Also give the application one well-designed write path; throwing dozens of competing writers at a single file is not a scalability strategy.&lt;/p&gt;

&lt;p&gt;Back up uploads and other application data too. Replicating only the database cannot recover files stored beside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add continuous replication with Litestream
&lt;/h2&gt;

&lt;p&gt;Litestream watches SQLite's WAL and streams changes to an S3-compatible bucket or another supported destination. Install it using the official release instructions, create a private bucket, and give Litestream credentials limited to the backup location it needs.&lt;/p&gt;

&lt;p&gt;Provide credentials through a protected service environment rather than writing them into your application repository. Then run replication as a system service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;litestream replicate /var/lib/myapp/app.db &lt;span class="se"&gt;\&lt;/span&gt;
  s3://your-backup-bucket/myapp/app.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command stays running while it replicates. Under systemd or another supervisor, configure it to start with the application host and restart on failure. Monitor replication errors and object-storage access; a silently stopped backup process is just an expensive source of false confidence.&lt;/p&gt;

&lt;p&gt;Keep retention for older snapshots where your storage setup supports it. Replication faithfully copies the current state, including a mistaken &lt;code&gt;DELETE&lt;/code&gt;, so point-in-time recovery options matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice the restore before you need it
&lt;/h2&gt;

&lt;p&gt;Stop the application or restore onto a separate test machine. Do not experiment on the only live database file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;litestream restore &lt;span class="nt"&gt;-o&lt;/span&gt; /var/lib/myapp-restored/app.db &lt;span class="se"&gt;\&lt;/span&gt;
  s3://your-backup-bucket/myapp/app.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the restored file and open it with the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sqlite3 /var/lib/myapp-restored/app.db &lt;span class="s1"&gt;'PRAGMA integrity_check;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then verify real records, not just that a file appeared. A restore drill should include starting the app, signing in, and reading recent data.&lt;/p&gt;

&lt;p&gt;Remember that continuous replication has a small recovery window: changes after the last successfully replicated update may be lost. Measure it instead of promising zero data loss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boring can be a feature
&lt;/h2&gt;

&lt;p&gt;SQLite plus Litestream gives a single-node application a local database and an off-box recovery path without running a separate database cluster. That is a genuinely useful production architecture for the right workload.&lt;/p&gt;

&lt;p&gt;Know the single-writer limits, keep backups off the machine, and rehearse recovery. If the application eventually needs multiple write nodes or heavy concurrent writes, migrate deliberately. Until then, one reliable file and a tested restore may be all the database infrastructure you need.&lt;/p&gt;

</description>
      <category>database</category>
      <category>sqlite</category>
      <category>litestream</category>
      <category>backups</category>
    </item>
    <item>
      <title>pgvector for Beginners: Add Semantic Search to PostgreSQL</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:09:53 +0000</pubDate>
      <link>https://dev.to/clinerds/pgvector-for-beginners-add-semantic-search-to-postgresql-4i4h</link>
      <guid>https://dev.to/clinerds/pgvector-for-beginners-add-semantic-search-to-postgresql-4i4h</guid>
      <description>&lt;p&gt;You already have PostgreSQL, backups, and a team that knows how to operate them. Then a new feature asks for semantic search, and suddenly the architecture diagram grows a separate vector database. For many applications, that extra system is unnecessary. &lt;strong&gt;pgvector&lt;/strong&gt; adds vector storage and similarity search to PostgreSQL itself.&lt;/p&gt;

&lt;p&gt;That means embeddings can live beside the documents, users, and permissions they belong to — with normal transactions, joins, and backups.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an embedding does
&lt;/h2&gt;

&lt;p&gt;An embedding model turns text, an image, or another input into a list of numbers. Similar inputs tend to produce vectors that are close together in the model's mathematical space.&lt;/p&gt;

&lt;p&gt;A keyword search for “database keeps running out of connections” might not match a paragraph about “connection pool exhaustion.” Semantic search can, because it compares meaning represented by embeddings rather than exact words.&lt;/p&gt;

&lt;p&gt;The model matters. Store the model name with your data, use the same model for documents and queries, and expect to regenerate embeddings when changing models.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tiny pgvector example
&lt;/h2&gt;

&lt;p&gt;Use a PostgreSQL distribution with pgvector installed, or try the project's Docker image. Enable the extension once per database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This table uses three dimensions so the example stays readable. Real models usually produce hundreds or thousands; the declared size must match your model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;bigserial&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Postgres connection pool exhausted'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0.9,0.1,0.0]'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Configure a reverse proxy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0.0,0.8,0.2]'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Tune PostgreSQL connections'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0.8,0.2,0.1]'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find the nearest rows using cosine distance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0.85,0.15,0.05]'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;similarity&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0.85,0.15,0.05]'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt; operator returns cosine distance, so &lt;code&gt;1 - distance&lt;/code&gt; expresses similarity. pgvector also supports L2 distance and inner product; use the metric recommended for your embedding model.&lt;/p&gt;

&lt;p&gt;In application code, pass the query embedding as a parameter. Do not build SQL by concatenating user input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add an index when exact search becomes slow
&lt;/h2&gt;

&lt;p&gt;Without a vector index, PostgreSQL performs exact nearest-neighbor search. That gives perfect recall and can be completely reasonable for a small table.&lt;/p&gt;

&lt;p&gt;As the data grows, an HNSW index can trade a little recall for faster approximate search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;documents_embedding_idx&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;hnsw&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;vector_cosine_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operator class must match the distance operator in your query. Check the plan and timing rather than assuming the index helps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0.85,0.15,0.05]'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Small tables may still prefer a sequential scan. On large production tables, build indexes concurrently and test recall against exact results. Our guide to &lt;a href="https://clinerds.com/article/postgresql-slow-queries-explain" rel="noopener noreferrer"&gt;debugging PostgreSQL queries with EXPLAIN&lt;/a&gt; explains how to read the plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep keywords in the picture
&lt;/h2&gt;

&lt;p&gt;Semantic search is not automatically better at everything. Exact error codes, product names, and rare identifiers often work better with keyword search. PostgreSQL full-text search can cover that side, and you can combine keyword and vector rankings for hybrid search.&lt;/p&gt;

&lt;p&gt;You also retain normal relational filters. A search can stay inside a tenant, workspace, or permission boundary without synchronizing access rules into another database. Design and test those filters carefully: approximate indexes and heavy filtering can interact in surprising ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  When pgvector is a good fit
&lt;/h2&gt;

&lt;p&gt;It is a strong default when PostgreSQL is already your primary database, your vector workload fits comfortably on it, and operational simplicity matters. It is especially useful for document search, recommendations, deduplication, and retrieval for AI features.&lt;/p&gt;

&lt;p&gt;A dedicated vector engine may be justified by extreme scale, specialized indexing, or a workload your benchmarks show PostgreSQL cannot handle. Make that decision with real data, not because every AI demo includes another service.&lt;/p&gt;

&lt;p&gt;Start with exact search, measure, and add HNSW when needed. You might discover that the simplest AI infrastructure improvement is using the database you already trust.&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>pgvector</category>
      <category>vectorsearch</category>
    </item>
    <item>
      <title>CrowdSec vs Fail2ban: Practical Brute-Force Protection for Your Server</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:09:32 +0000</pubDate>
      <link>https://dev.to/clinerds/crowdsec-vs-fail2ban-practical-brute-force-protection-for-your-server-28a6</link>
      <guid>https://dev.to/clinerds/crowdsec-vs-fail2ban-practical-brute-force-protection-for-your-server-28a6</guid>
      <description>&lt;p&gt;Expose SSH or a website to the internet and the automated login attempts will find it. You do not need to read thousands of failed-password lines by hand, but you do need a sensible way to stop repetitive abuse. &lt;strong&gt;Fail2ban&lt;/strong&gt; and &lt;strong&gt;CrowdSec&lt;/strong&gt; both turn suspicious activity into firewall decisions.&lt;/p&gt;

&lt;p&gt;They overlap, but they are not quite the same product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail2ban: small, local, and predictable
&lt;/h2&gt;

&lt;p&gt;Fail2ban watches logs, matches patterns called filters, and bans an address through a firewall action when a jail reaches its threshold. It has been doing that job for years, and for a single Linux server that is often exactly what you need.&lt;/p&gt;

&lt;p&gt;On Debian or Ubuntu:&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;fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;/etc/fail2ban/jail.local&lt;/code&gt; instead of editing the packaged defaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[DEFAULT]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="py"&gt;findtime&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="s"&gt;10m&lt;/span&gt;
&lt;span class="py"&gt;maxretry&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="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;bantime&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="s"&gt;1h&lt;/span&gt;
&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nn"&gt;[sshd]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="py"&gt;enabled&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="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart it and inspect the jail:&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 fail2ban
&lt;span class="nb"&gt;sudo &lt;/span&gt;fail2ban-client status sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adjust the backend only if your distribution does not read SSH logs correctly. Test from a system you control, and keep an active root or console session while changing firewall behavior so a bad rule cannot lock you out completely.&lt;/p&gt;

&lt;p&gt;Fail2ban is easy to reason about: this log, this pattern, this threshold, this ban. It does not require a central account or a crowd to be useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  CrowdSec: behavior detection plus shared intelligence
&lt;/h2&gt;

&lt;p&gt;CrowdSec also parses logs and detects behavior locally. Its architecture separates the detection engine from &lt;strong&gt;bouncers&lt;/strong&gt;, which enforce decisions in a firewall, reverse proxy, or another layer.&lt;/p&gt;

&lt;p&gt;That split is useful when you want one place to detect abuse and another to block it. CrowdSec can also consume community intelligence about addresses exhibiting malicious behavior elsewhere, while sharing signals according to its configuration. Treat that as an additional data source, not as proof that every listed address attacked you personally.&lt;/p&gt;

&lt;p&gt;Typical inspection commands are:&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;cscli alerts list
&lt;span class="nb"&gt;sudo &lt;/span&gt;cscli decisions list
&lt;span class="nb"&gt;sudo &lt;/span&gt;cscli metrics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The metrics view is particularly important: it shows which log sources are being read and which parsers are working. Installing a security agent without confirming that it parses your actual logs creates comforting dashboards and very little protection.&lt;/p&gt;

&lt;p&gt;Use the official installation documentation for your distribution, install the relevant collections for SSH or your web server, and add a firewall bouncer. Then generate a harmless test event or use CrowdSec's testing facilities rather than attacking your own public endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one should you choose?
&lt;/h2&gt;

&lt;p&gt;Choose &lt;strong&gt;Fail2ban&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have one or a few servers.&lt;/li&gt;
&lt;li&gt;SSH brute force and a small set of log patterns are the main concern.&lt;/li&gt;
&lt;li&gt;You want minimal moving parts and fully local decisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose &lt;strong&gt;CrowdSec&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want a broader scenario ecosystem for web and service abuse.&lt;/li&gt;
&lt;li&gt;Detection and enforcement need to happen in different places.&lt;/li&gt;
&lt;li&gt;Community threat intelligence is valuable to your setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running both against the same logs is possible, but it also creates overlapping bans and two systems to debug. For a small VPS, pick one and operate it well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Neither tool fixes weak security
&lt;/h2&gt;

&lt;p&gt;An IP ban is a reaction, not an authentication strategy. Disable SSH password login where practical, use keys, keep software updated, and restrict exposed ports. Our &lt;a href="https://clinerds.com/article/how-to-install-and-configure-fail2ban-in-centos-8-prevent-from-bruteforce-attack" rel="noopener noreferrer"&gt;Fail2ban installation guide&lt;/a&gt; and &lt;a href="https://clinerds.com/article/hardening-ssh" rel="noopener noreferrer"&gt;SSH hardening guide&lt;/a&gt; cover those fundamentals.&lt;/p&gt;

&lt;p&gt;Also watch false positives. A shared NAT address can represent many innocent users, and overly aggressive thresholds can ban your own office. Start with conservative rules, review alerts, and know how to unban an address.&lt;/p&gt;

&lt;p&gt;For most home servers, Fail2ban remains a perfectly good answer. For richer web detection and distributed enforcement, CrowdSec earns its extra complexity. The best choice is the one whose logs, decisions, and recovery commands you actually understand.&lt;/p&gt;

</description>
      <category>security</category>
      <category>crowdsec</category>
      <category>fail2ban</category>
      <category>ssh</category>
    </item>
    <item>
      <title>Passkeys Explained: A Practical Move Beyond Passwords</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:09:11 +0000</pubDate>
      <link>https://dev.to/clinerds/passkeys-explained-a-practical-move-beyond-passwords-4850</link>
      <guid>https://dev.to/clinerds/passkeys-explained-a-practical-move-beyond-passwords-4850</guid>
      <description>&lt;p&gt;Passwords have two problems that never really go away: people reuse them, and fake login pages are good at stealing them. &lt;strong&gt;Passkeys&lt;/strong&gt; offer a different model. Instead of typing a shared secret into every website, your device proves it holds a private key that belongs to your account.&lt;/p&gt;

&lt;p&gt;That sounds technical, but using one usually feels like unlocking your phone. The important changes happen underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a passkey actually is
&lt;/h2&gt;

&lt;p&gt;When you create a passkey, your device generates a pair of cryptographic keys for that specific site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;private key&lt;/strong&gt; stays protected by your device or password manager.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;public key&lt;/strong&gt; goes to the website.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At login, the site sends a challenge. Your device signs it with the private key, and the site verifies the signature with the public key. The private key is never sent to the site.&lt;/p&gt;

&lt;p&gt;Your fingerprint, face, or device PIN unlocks access to the key locally. The website does not receive your fingerprint or face scan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why phishing works less well
&lt;/h2&gt;

&lt;p&gt;A password can be typed into the wrong website. A passkey is bound to the site's origin, so a credential created for &lt;code&gt;example.com&lt;/code&gt; is not used on &lt;code&gt;exarnple.com&lt;/code&gt;, no matter how convincing the page looks.&lt;/p&gt;

&lt;p&gt;This removes one of the most reliable tricks in account takeover. It also removes the password database's usual treasure: public keys stored by a service are not reusable login secrets.&lt;/p&gt;

&lt;p&gt;Passkeys do not make malware harmless, protect an already-compromised session, or stop someone from tricking you into approving a different action. They solve a specific problem very well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Synced passkeys and device-bound keys
&lt;/h2&gt;

&lt;p&gt;Many passkeys synchronize through an ecosystem or password manager, making them available on your phone and laptop. Others remain bound to a hardware security key or particular device.&lt;/p&gt;

&lt;p&gt;Synced passkeys are convenient and still use public-key cryptography. Device-bound credentials can be useful for stricter policies, but losing the device matters more. The right choice depends on the account and your recovery plan.&lt;/p&gt;

&lt;p&gt;Before turning passkeys on for an important account:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add them on more than one trusted device where the service supports it.&lt;/li&gt;
&lt;li&gt;Keep a hardware security key or another independent recovery method if possible.&lt;/li&gt;
&lt;li&gt;Store recovery codes somewhere safe and offline.&lt;/li&gt;
&lt;li&gt;Test signing in on a second device before removing an old login method.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Losing every device should not mean losing your digital life.&lt;/p&gt;

&lt;h2&gt;
  
  
  What websites need to implement
&lt;/h2&gt;

&lt;p&gt;Passkeys use the WebAuthn standard and platform authenticators. For developers, the browser API is only half the job. The server must generate unpredictable challenges, verify responses and origins, store credential public keys, and expire challenge state correctly.&lt;/p&gt;

&lt;p&gt;Use a maintained WebAuthn library rather than writing signature verification yourself. Keep the existing account-recovery and session-security controls, require HTTPS, and give users a way to name and revoke credentials.&lt;/p&gt;

&lt;p&gt;A sensible rollout offers passkeys alongside existing sign-in first. Watch successful registrations, sign-ins, and recovery requests before considering whether passwords can become optional. Users with older devices still need a workable path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do passkeys replace password managers?
&lt;/h2&gt;

&lt;p&gt;Not exactly. Many password managers can create and sync passkeys, so the two increasingly work together. You may still have passwords for older sites, SSH keys, encrypted archives, and other secrets. A manager remains useful — and if you want to run one yourself, our &lt;a href="https://clinerds.com/article/vaultwarden-self-hosted-password-manager" rel="noopener noreferrer"&gt;Vaultwarden guide&lt;/a&gt; is a practical starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you switch?
&lt;/h2&gt;

&lt;p&gt;For accounts that offer passkeys, yes: they are usually easier to use and much harder to phish than a password alone. Start with your email, password manager, cloud accounts, and developer platforms, because those can unlock many other services.&lt;/p&gt;

&lt;p&gt;Just do not confuse “no password” with “no recovery plan.” Add a passkey, add a backup path, and then enjoy typing fewer secrets into boxes.&lt;/p&gt;

</description>
      <category>security</category>
      <category>passkeys</category>
      <category>webauthn</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Caddy vs Nginx: Choosing a Reverse Proxy You Can Actually Maintain</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:08:50 +0000</pubDate>
      <link>https://dev.to/clinerds/caddy-vs-nginx-choosing-a-reverse-proxy-you-can-actually-maintain-2hkc</link>
      <guid>https://dev.to/clinerds/caddy-vs-nginx-choosing-a-reverse-proxy-you-can-actually-maintain-2hkc</guid>
      <description>&lt;p&gt;You have an app listening on port &lt;code&gt;3000&lt;/code&gt;, a domain pointed at your server, and one remaining job: put HTTPS in front of it. The two names you will see most often are &lt;strong&gt;Caddy&lt;/strong&gt; and &lt;strong&gt;Nginx&lt;/strong&gt;. Both are excellent reverse proxies. The better choice depends less on benchmark charts than on how you want to operate the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caddy: HTTPS as the default
&lt;/h2&gt;

&lt;p&gt;Caddy's standout feature is automatic HTTPS. Give it a public domain with correct DNS and reachable ports 80 and 443, and it obtains and renews certificates for you.&lt;/p&gt;

&lt;p&gt;A complete reverse-proxy configuration can be this small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;blog.example.com

reverse_proxy 127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload it with:&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;caddy reload &lt;span class="nt"&gt;--config&lt;/span&gt; /etc/caddy/Caddyfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire mental model: domain in, application behind it, certificate handled. Caddy also makes security-conscious defaults easy to keep because you are not maintaining a separate certificate bot and a pile of TLS boilerplate.&lt;/p&gt;

&lt;p&gt;For a new VPS, a homelab dashboard, or a handful of small applications, this simplicity is a real operational advantage. Fewer lines mean fewer places to make a mistake at midnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nginx: control and a huge ecosystem
&lt;/h2&gt;

&lt;p&gt;Nginx needs a little more configuration, but it is everywhere. Most teams already have someone who understands it, and its modules, caching controls, load balancing, and documentation cover almost every edge case.&lt;/p&gt;

&lt;p&gt;An equivalent site looks like this:&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;blog.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:3000&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-Real-IP&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-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&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;You then add TLS and HTTP-to-HTTPS redirection, commonly with Certbot or another certificate automation tool. Our &lt;a href="https://clinerds.com/article/nginx-reverse-proxy-explained" rel="noopener noreferrer"&gt;Nginx reverse proxy guide&lt;/a&gt; and &lt;a href="https://clinerds.com/article/lets-encrypt-certbot-auto-renew" rel="noopener noreferrer"&gt;Let's Encrypt renewal guide&lt;/a&gt; walk through those pieces.&lt;/p&gt;

&lt;p&gt;Nginx is a natural choice when you need detailed caching rules, existing include files, specialized modules, or consistency with infrastructure your team already runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comparison that matters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Configuration:&lt;/strong&gt; Caddy is shorter for common reverse-proxy and static-site setups. Nginx exposes more of the machinery explicitly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certificates:&lt;/strong&gt; Caddy automates HTTPS in its normal configuration path. Nginx usually pairs with a separate ACME client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Familiarity:&lt;/strong&gt; Nginx has an enormous installed base and years of examples. Caddy's documentation and Caddyfile are friendly, but unusual requirements may push you toward its JSON configuration or plugins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance:&lt;/strong&gt; either can serve a typical blog or application without being the bottleneck. Your app, database, TLS termination pattern, caching, and server size usually matter more. Benchmark your own workload before rewriting a working proxy for a theoretical gain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operations:&lt;/strong&gt; Caddy reduces routine certificate work. Nginx rewards teams that want precise control and already have proven configuration management.&lt;/p&gt;

&lt;h2&gt;
  
  
  A safe migration path
&lt;/h2&gt;

&lt;p&gt;Do not replace a healthy production proxy on a Friday because a comparison article told you to. Test the new configuration on a separate hostname or VM first.&lt;/p&gt;

&lt;p&gt;For either proxy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Point a test DNS record at the server and wait for it to resolve publicly.&lt;/li&gt;
&lt;li&gt;Allow only the required web ports through the firewall.&lt;/li&gt;
&lt;li&gt;Check proxy headers so the application sees the real scheme and client address.&lt;/li&gt;
&lt;li&gt;Verify certificate renewal, not just the first certificate issuance.&lt;/li&gt;
&lt;li&gt;Test uploads, long requests, WebSockets if used, and redirect behavior.&lt;/li&gt;
&lt;li&gt;Move production only after a rollback path is clear.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  My practical recommendation
&lt;/h2&gt;

&lt;p&gt;Choose &lt;strong&gt;Caddy&lt;/strong&gt; for a new, straightforward self-hosted service when automatic HTTPS and a short configuration are the priority. Choose &lt;strong&gt;Nginx&lt;/strong&gt; when existing expertise, advanced routing, or a mature configuration already makes it the easier system to maintain.&lt;/p&gt;

&lt;p&gt;The best proxy is not the one with the loudest benchmark. It is the one you can configure correctly, renew automatically, and troubleshoot without dread.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>caddy</category>
      <category>nginx</category>
      <category>reverseproxy</category>
    </item>
    <item>
      <title>DNS over HTTPS Explained: What It Protects and What It Doesn't</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:08:29 +0000</pubDate>
      <link>https://dev.to/clinerds/dns-over-https-explained-what-it-protects-and-what-it-doesnt-4pa2</link>
      <guid>https://dev.to/clinerds/dns-over-https-explained-what-it-protects-and-what-it-doesnt-4pa2</guid>
      <description>&lt;p&gt;Every time you visit a website, your device usually asks DNS for its IP address first. Traditional DNS sends that question without encryption, so the network carrying it can often see or tamper with the request. &lt;strong&gt;DNS over HTTPS (DoH)&lt;/strong&gt; fixes that transport problem by placing DNS inside an HTTPS connection.&lt;/p&gt;

&lt;p&gt;It is a useful privacy upgrade, but it is not an invisibility cloak. Understanding what it does — and what it does not — helps you choose a sensible setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes with DoH
&lt;/h2&gt;

&lt;p&gt;Without DoH, a typical lookup travels over UDP or TCP port 53. With DoH, the client sends the DNS query to a compatible resolver over HTTPS, normally on port 443.&lt;/p&gt;

&lt;p&gt;That protects the query from casual inspection and modification on the path between you and the resolver. Public Wi-Fi operators and intermediate routers no longer get the same easy view of those DNS requests.&lt;/p&gt;

&lt;p&gt;Your chosen resolver still receives the queries. Your device still connects to the destination IP afterward. DoH shifts trust; it does not eliminate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  DoH, DoT, and ordinary DNS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DNS over TLS (DoT)&lt;/strong&gt; solves a similar transport problem using a dedicated TLS service, usually on port 853. DoH blends into HTTPS traffic and can be easier to deploy through restrictive networks. Neither is automatically “more anonymous.”&lt;/p&gt;

&lt;p&gt;The useful comparison is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plain DNS:&lt;/strong&gt; simple and widely supported, but unencrypted between client and resolver.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DoT:&lt;/strong&gt; encrypted DNS with a distinct port and identity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DoH:&lt;/strong&gt; encrypted DNS carried through HTTPS, often configured in browsers and operating systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A local resolver such as Unbound or Pi-hole can still provide filtering and caching while forwarding upstream over an encrypted protocol. That gives you central control without sending every device's queries in the clear to an outside resolver.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn it on without breaking your network
&lt;/h2&gt;

&lt;p&gt;The easiest place to test is a browser that supports encrypted DNS. Choose a resolver you trust, enable its secure-DNS option, and visit that provider's test page to confirm the browser is using it.&lt;/p&gt;

&lt;p&gt;Then consider the rest of the system. Browser-only DoH does not automatically protect lookups made by other applications. Operating-system support provides broader coverage, but availability and behavior differ across platforms.&lt;/p&gt;

&lt;p&gt;On a home network, decide who should control DNS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you rely on Pi-hole or AdGuard Home for filtering, do not let every device silently bypass it with different DoH settings.&lt;/li&gt;
&lt;li&gt;If you manage children's devices or a workplace network, document the approved resolvers rather than discovering unexpected policies later.&lt;/li&gt;
&lt;li&gt;If an application has its own DNS setting, remember that changing the router alone may not change that application's behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a refresher on the records behind the lookup, see our &lt;a href="https://clinerds.com/article/dns-records-explained" rel="noopener noreferrer"&gt;DNS records guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The privacy claims to ignore
&lt;/h2&gt;

&lt;p&gt;DoH does not hide all website metadata. The destination IP remains visible, and the TLS handshake can reveal the hostname unless additional protections such as Encrypted Client Hello apply. A resolver can also observe query patterns even when the transport is encrypted.&lt;/p&gt;

&lt;p&gt;DoH is not a VPN. It does not route the rest of your traffic through another network, change your public IP, or make an unsafe website safe.&lt;/p&gt;

&lt;p&gt;It also cannot guarantee integrity all the way to an authoritative server. It protects the client-to-resolver leg; DNSSEC addresses a different problem by allowing DNS responses to be validated cryptographically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you use it?
&lt;/h2&gt;

&lt;p&gt;For a laptop on untrusted Wi-Fi, encrypted DNS is a sensible default. Choose a resolver with a clear privacy policy and good reliability, then verify that it works with your filtering and local hostnames.&lt;/p&gt;

&lt;p&gt;For a homelab, central DNS plus encrypted upstream forwarding is often cleaner than a different browser setting on every device. For a business, make it a deliberate policy rather than an accidental mix of browser defaults.&lt;/p&gt;

&lt;p&gt;The win is straightforward: fewer people on the network path can watch or alter your lookups. Just do not mistake one encrypted hop for complete privacy.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>dns</category>
      <category>doh</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Build Your Own Media Server with Jellyfin and Docker</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:08:09 +0000</pubDate>
      <link>https://dev.to/clinerds/build-your-own-media-server-with-jellyfin-and-docker-348</link>
      <guid>https://dev.to/clinerds/build-your-own-media-server-with-jellyfin-and-docker-348</guid>
      <description>&lt;p&gt;Streaming subscriptions are convenient until a film disappears, a show moves to another service, or you realize your own media collection is sitting on a hard drive doing nothing. &lt;strong&gt;Jellyfin&lt;/strong&gt; is a free, open-source media server that lets you stream that collection to your browser, TV, phone, and other clients — without an account at a media company.&lt;/p&gt;

&lt;p&gt;It is also a good first self-hosting project: useful immediately, easy to run in Docker, and forgiving enough to learn on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before installing: organize the media
&lt;/h2&gt;

&lt;p&gt;Jellyfin can fetch artwork and metadata, but it needs to identify what it is looking at. A simple structure prevents hours of mismatched titles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/srv/media/
├── Movies/
│   └── Example Film (2024)/
│       └── Example Film (2024).mkv
└── Shows/
    └── Example Show/
        └── Season 01/
            └── Example Show - S01E01.mkv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use your real titles, of course. Keep films and shows in separate libraries. Read-only media mounts are a sensible default: Jellyfin needs to read your collection, not reorganize or delete it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run Jellyfin with Docker Compose
&lt;/h2&gt;

&lt;p&gt;Create a &lt;code&gt;compose.yaml&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;jellyfin&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;jellyfin/jellyfin: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;jellyfin&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;1000:1000&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;/srv/jellyfin/config:/config&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/srv/jellyfin/cache:/cache&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/srv/media:/media:ro&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;8096:8096"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the writable directories, make sure UID/GID &lt;code&gt;1000&lt;/code&gt; owns them, then start the service:&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 mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /srv/jellyfin/&lt;span class="o"&gt;{&lt;/span&gt;config,cache&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 1000:1000 /srv/jellyfin
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://server-ip:8096&lt;/code&gt;. Create the administrator account, add libraries pointing to &lt;code&gt;/media/Movies&lt;/code&gt; and &lt;code&gt;/media/Shows&lt;/code&gt;, and let the initial scan finish.&lt;/p&gt;

&lt;p&gt;For a long-lived installation, replace &lt;code&gt;latest&lt;/code&gt; with a release you have tested, and upgrade deliberately rather than pulling random updates every night.&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct play first, transcoding second
&lt;/h2&gt;

&lt;p&gt;The smoothest playback happens when the client supports the file directly. Transcoding converts video in real time when it does not, and high-resolution software transcoding can overwhelm an otherwise capable little server.&lt;/p&gt;

&lt;p&gt;If you have a supported Intel or AMD GPU, hardware acceleration can help. On Linux, that commonly means passing the render device into the container:&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;devices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/dev/dri:/dev/dri&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add that only if &lt;code&gt;/dev/dri&lt;/code&gt; exists, then enable the appropriate acceleration method in Jellyfin's playback settings. The exact driver and permissions vary by hardware. Check the server dashboard's transcoding logs if playback still maxes out the CPU.&lt;/p&gt;

&lt;p&gt;For remote viewing, a modest upload speed matters more than a powerful processor. Lower the client's streaming bitrate instead of expecting your home connection to perform miracles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the library pleasant to use
&lt;/h2&gt;

&lt;p&gt;Install the official Jellyfin client for your TV or streaming device where one is available, and test direct playback with a representative file before importing everything. A phone browser is useful for setup, but TV clients have different codec and subtitle support.&lt;/p&gt;

&lt;p&gt;After the first scan, check a handful of titles for incorrect matches. Fix folder names or use the library's identify function rather than editing dozens of metadata fields manually. Keep extras and different editions separated clearly so the scanner does not merge them into a confusing entry.&lt;/p&gt;

&lt;p&gt;Schedule library scans and metadata refreshes for a quiet time if the server runs on a small disk or low-power CPU. Also watch the config partition's free space: transcoding cache and logs should not be allowed to fill it. A full system disk can make an otherwise healthy media server look broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it private and recoverable
&lt;/h2&gt;

&lt;p&gt;Do not forward port &lt;code&gt;8096&lt;/code&gt; to the internet just because remote access sounds useful. A private overlay such as &lt;a href="https://clinerds.com/article/tailscale-homelab-remote-access" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt; is a cleaner default for personal viewing.&lt;/p&gt;

&lt;p&gt;Back up &lt;code&gt;/srv/jellyfin/config&lt;/code&gt; as well as any custom metadata and artwork you care about. Your media files need their own backup too — a server disk is not a backup. The &lt;a href="https://clinerds.com/article/self-hosted-backup-strategy-3-2-1" rel="noopener noreferrer"&gt;3-2-1 backup strategy&lt;/a&gt; is a good model.&lt;/p&gt;

&lt;p&gt;Finally, keep Jellyfin updated, use separate accounts for viewers, and leave remote administration disabled unless you need it. The goal is a boring media appliance: press play, watch your stuff, and spend your weekend enjoying the library instead of maintaining it.&lt;/p&gt;

</description>
      <category>selfhosting</category>
      <category>jellyfin</category>
      <category>docker</category>
      <category>mediaserver</category>
    </item>
    <item>
      <title>Tailscale for Homelabs: Access Your Servers Without Port Forwarding</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:07:48 +0000</pubDate>
      <link>https://dev.to/clinerds/tailscale-for-homelabs-access-your-servers-without-port-forwarding-2ge3</link>
      <guid>https://dev.to/clinerds/tailscale-for-homelabs-access-your-servers-without-port-forwarding-2ge3</guid>
      <description>&lt;p&gt;You finally have Jellyfin, Nextcloud, and a dashboard running at home. Then you leave the house and realize none of it is reachable. The usual answer is port forwarding, but exposing every self-hosted admin panel to the internet creates a bigger problem than it solves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tailscale&lt;/strong&gt; gives your devices a private network built on WireGuard. Your phone, laptop, and home server can talk to each other without opening inbound ports on your router, even behind carrier-grade NAT.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Tailscale actually changes
&lt;/h2&gt;

&lt;p&gt;Each device joins your private &lt;strong&gt;tailnet&lt;/strong&gt; and receives a stable private IP address. Traffic between devices is encrypted. Tailscale's coordination service helps devices find one another and exchange keys; in many cases, the data then travels directly between them.&lt;/p&gt;

&lt;p&gt;This is different from publishing a service on the web. By default, only devices and users allowed into your tailnet can reach it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect your first server
&lt;/h2&gt;

&lt;p&gt;On a Debian or Ubuntu server, the official installer is the quickest start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://tailscale.com/install.sh | sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;tailscale up
tailscale ip &lt;span class="nt"&gt;-4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second command prints a login URL. Open it, authenticate, and approve the machine. Install the Tailscale app on your laptop or phone and sign in to the same tailnet.&lt;/p&gt;

&lt;p&gt;Now test the private address from another connected device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping 100.x.y.z
ssh your-user@100.x.y.z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the placeholder with the address returned by &lt;code&gt;tailscale ip -4&lt;/code&gt;. You can now reach services using &lt;code&gt;http://100.x.y.z:8096&lt;/code&gt; or enable MagicDNS in the admin console and use the machine name instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Share a web app without exposing it publicly
&lt;/h2&gt;

&lt;p&gt;If Jellyfin listens on port &lt;code&gt;8096&lt;/code&gt;, Tailscale Serve can provide a tailnet-only HTTPS URL:&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;tailscale serve &lt;span class="nt"&gt;--bg&lt;/span&gt; 8096
tailscale serve status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the HTTPS address shown in the output from a connected device. To remove it later:&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;tailscale serve &lt;span class="nt"&gt;--bg&lt;/span&gt; off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Serve is for your private tailnet. &lt;strong&gt;Tailscale Funnel&lt;/strong&gt; is the separate feature that can expose a service to the public internet — don't enable it just to share an admin dashboard with yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the setup safer than a flat home network
&lt;/h2&gt;

&lt;p&gt;A private network is not automatically a zero-trust network. Do these things next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use individual accounts&lt;/strong&gt; instead of sharing one login across the whole household.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrict access with ACLs or grants.&lt;/strong&gt; A media player does not need SSH access to every server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep normal SSH hardening in place&lt;/strong&gt; if you run &lt;code&gt;tailscale ssh&lt;/code&gt;; tailnet access is another layer, not a reason to forget the operating system. Our &lt;a href="https://clinerds.com/article/hardening-ssh" rel="noopener noreferrer"&gt;SSH hardening guide&lt;/a&gt; covers the basics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review connected machines&lt;/strong&gt; and remove old phones or decommissioned servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep key expiry enabled for personal devices.&lt;/strong&gt; Use properly scoped auth keys for unattended machines instead of disabling every safeguard.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When a device cannot connect
&lt;/h2&gt;

&lt;p&gt;Start with &lt;code&gt;tailscale status&lt;/code&gt; on both machines. Confirm they are logged into the intended tailnet, online, and not using an expired key. Then check that the service is actually listening on the server's Tailscale interface or all interfaces; an application bound only to &lt;code&gt;127.0.0.1&lt;/code&gt; will not answer connections to its &lt;code&gt;100.x.y.z&lt;/code&gt; address.&lt;/p&gt;

&lt;p&gt;Next, check the host firewall and the tailnet access policy. A working WireGuard tunnel cannot make a closed application port open, and an access rule may intentionally block one user or device. Change one layer at a time so you know which setting fixed the problem.&lt;/p&gt;

&lt;p&gt;If direct peer-to-peer connectivity fails on a restrictive network, Tailscale may relay traffic through a DERP server. That is slower but still encrypted end to end. It is a connectivity fallback, not a reason to start forwarding ports again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it fits — and where it doesn't
&lt;/h2&gt;

&lt;p&gt;Tailscale is excellent for remote administration, private dashboards, and letting family reach a media server without teaching them VPN configuration. It can also connect cloud VMs and home machines without building a fragile chain of port forwards.&lt;/p&gt;

&lt;p&gt;It does not replace backups, application passwords, or software updates. It also relies on Tailscale's coordination infrastructure unless you run a compatible self-hosted control plane such as Headscale. If you want every component under your control and enjoy operating network infrastructure, plain WireGuard may be a better fit.&lt;/p&gt;

&lt;p&gt;For most homelabs, though, this is the sensible first step: close the forwarded ports, connect your devices privately, and expose public services only when they genuinely need to be public.&lt;/p&gt;

</description>
      <category>selfhosting</category>
      <category>tailscale</category>
      <category>wireguard</category>
      <category>homelab</category>
    </item>
    <item>
      <title>OpenTelemetry for Beginners: Traces, Metrics, and Your First Setup</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:07:27 +0000</pubDate>
      <link>https://dev.to/clinerds/opentelemetry-for-beginners-traces-metrics-and-your-first-setup-go3</link>
      <guid>https://dev.to/clinerds/opentelemetry-for-beginners-traces-metrics-and-your-first-setup-go3</guid>
      <description>&lt;p&gt;"Something is slow" is the least actionable sentence in operations. Is it the app? The database? The network between them? &lt;strong&gt;OpenTelemetry (OTel)&lt;/strong&gt; is how you stop guessing: a vendor-neutral standard for collecting traces, metrics, and logs from your applications — so you can see exactly where time and errors go.&lt;/p&gt;

&lt;p&gt;It has become the default instrumentation layer for new projects, and for good reason: you instrument once and send data to Jaeger, Grafana Tempo, Prometheus, Datadog, or whatever backend you choose. No vendor lock-in at the SDK level.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three signals, in plain English
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Traces&lt;/strong&gt; — the path of a single request through your system: which services it touched and how long each step took. This is what answers "slow &lt;em&gt;where&lt;/em&gt;?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt; — numbers over time: request rate, error rate, latency percentiles, memory. This is what dashboards and alerts are made of.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs&lt;/strong&gt; — the events you already know, but correlated with traces so you can jump from "this slow request" to its exact log lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need all three on day one. Start with traces — they deliver the biggest "oh, &lt;em&gt;that's&lt;/em&gt; the problem" moments.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture in 30 seconds
&lt;/h2&gt;

&lt;p&gt;Your app uses an OTel SDK (or auto-instrumentation) to produce telemetry. It exports to the &lt;strong&gt;OpenTelemetry Collector&lt;/strong&gt; — a small agent that receives, batches, and forwards data to your backend. The Collector is the key piece: it keeps your app config trivial and your backend choices flexible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App (SDK) → OTel Collector → Jaeger / Grafana / Prometheus / vendor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A minimal working setup with Docker
&lt;/h2&gt;

&lt;p&gt;Run Jaeger (an all-in-one trace backend with a UI) and the Collector:&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;otel-collector&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;otel/opentelemetry-collector:latest&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--config=/etc/otelcol/config.yaml"&lt;/span&gt;&lt;span class="pi"&gt;]&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;./otelcol-config.yaml:/etc/otelcol/config.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;4318:4318"&lt;/span&gt;   &lt;span class="c1"&gt;# OTLP HTTP receiver&lt;/span&gt;

  &lt;span class="na"&gt;jaeger&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;jaegertracing/all-in-one: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;COLLECTOR_OTLP_ENABLED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&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;16686:16686"&lt;/span&gt; &lt;span class="c1"&gt;# Jaeger UI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Collector config (&lt;code&gt;otelcol-config.yaml&lt;/code&gt;) receives OTLP and exports to Jaeger:&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;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;protocols&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jaeger:4317&lt;/span&gt;
    &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;insecure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;traces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Instrument a Node.js app
&lt;/h2&gt;

&lt;p&gt;Auto-instrumentation covers HTTP, Express, and many libraries out of the box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tracing.js — import this BEFORE anything else&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NodeSDK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getNodeAutoInstrumentations&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/auto-instrumentations-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NodeSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;instrumentations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;getNodeAutoInstrumentations&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point the exporter at the Collector (default is OTLP HTTP on &lt;code&gt;localhost:4318&lt;/code&gt;), restart the app, generate some traffic, and open &lt;code&gt;http://localhost:16686&lt;/code&gt;. Your requests appear as traces: every HTTP call, every DB query, with durations. That slow endpoint now shows you exactly which hop eats the time.&lt;/p&gt;

&lt;p&gt;Python, Go, Java, and .NET have equivalent auto-instrumentation packages — same pattern, same backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Find your slowest endpoint&lt;/strong&gt; in the trace list, open a trace, look at the longest span. That's your optimization target, with proof.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add custom spans&lt;/strong&gt; around your own expensive operations (image processing, report generation) so they show up too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add metrics later&lt;/strong&gt;: the same SDKs expose latency and error-rate metrics for dashboards and alerts.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Keep the telemetry bill under control
&lt;/h2&gt;

&lt;p&gt;Tracing every request forever can create more data than you can afford to store, especially once several services are instrumented. Start with a small service and learn what a useful trace looks like. Then configure sampling appropriate to your traffic: enough successful requests to understand normal behavior, while preserving errors and slow outliers where your backend supports it.&lt;/p&gt;

&lt;p&gt;Be careful with span attributes and metric labels. Values such as user IDs, full URLs with unique identifiers, or request IDs create high-cardinality data that is expensive to index. Never put passwords, authorization headers, session tokens, or personal request payloads into telemetry just because the instrumentation makes it easy.&lt;/p&gt;

&lt;p&gt;The Collector is also part of your production system. Monitor its queue, dropped data, memory use, and exporter errors. If it cannot reach the backend, your application should keep serving users rather than becoming a casualty of the observability pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical takeaway
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry isn't a monitoring product — it's the plumbing that makes any monitoring product useful without locking you in. Start with one service, one Collector, and Jaeger. The first time you watch a slow request break down hop by hop, you'll wonder how you ever debugged with logs alone.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>opentelemetry</category>
      <category>observability</category>
      <category>tracing</category>
    </item>
    <item>
      <title>GitOps with ArgoCD: A Practical Beginner's Guide for Kubernetes</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:07:06 +0000</pubDate>
      <link>https://dev.to/clinerds/gitops-with-argocd-a-practical-beginners-guide-for-kubernetes-2j76</link>
      <guid>https://dev.to/clinerds/gitops-with-argocd-a-practical-beginners-guide-for-kubernetes-2j76</guid>
      <description>&lt;p&gt;"What's running in production right now?" If the honest answer is "whatever someone last applied with kubectl," you have a configuration drift problem waiting to happen. &lt;strong&gt;GitOps&lt;/strong&gt; fixes it with one rule: the desired state of your cluster lives in Git, and an agent makes the cluster match it. &lt;strong&gt;ArgoCD&lt;/strong&gt; is the most popular agent for the job.&lt;/p&gt;

&lt;p&gt;Here's how the model works and how to set it up for real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea in one paragraph
&lt;/h2&gt;

&lt;p&gt;You stop running &lt;code&gt;kubectl apply&lt;/code&gt; by hand. Instead, your manifests (or Helm charts, or Kustomize overlays) live in a Git repository. ArgoCD watches both the repo and the cluster, shows you when they differ, and syncs them — automatically or at the click of a button. Every production change becomes a pull request, with history, review, and an instant rollback via &lt;code&gt;git revert&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install ArgoCD on your cluster
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace argocd
kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; argocd &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the pods to come up, then get the admin password and expose the UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; argocd get secret argocd-initial-admin-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;

kubectl port-forward svc/argocd-server &lt;span class="nt"&gt;-n&lt;/span&gt; argocd 8080:443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;https://localhost:8080&lt;/code&gt;, log in as &lt;code&gt;admin&lt;/code&gt; with that password, and change it immediately. For a permanent setup, front it with an ingress and TLS — our &lt;a href="https://clinerds.com/article/kubernetes-cookbook-ingress" rel="noopener noreferrer"&gt;Kubernetes ingress guide&lt;/a&gt; covers that part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Point it at a repository
&lt;/h2&gt;

&lt;p&gt;The fastest way to learn is a small app repo of your own: a folder with &lt;code&gt;deployment.yaml&lt;/code&gt; and &lt;code&gt;service.yaml&lt;/code&gt; for something trivial (an Nginx deployment works fine).&lt;/p&gt;

&lt;p&gt;Create an Application in the UI — or as YAML, which is more in the spirit of GitOps:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;demo&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/yourname/gitops-demo.git&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manifests&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://kubernetes.default.svc&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo&lt;/span&gt;
  &lt;span class="na"&gt;syncPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;prune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;selfHeal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;syncOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CreateNamespace=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;automated&lt;/code&gt; means ArgoCD syncs on every commit, &lt;code&gt;prune&lt;/code&gt; removes resources deleted from Git, and &lt;code&gt;selfHeal&lt;/code&gt; reverts manual changes made directly in the cluster — that's the drift-killer.&lt;/p&gt;

&lt;p&gt;Apply it with &lt;code&gt;kubectl apply -f application.yaml&lt;/code&gt; and watch the UI: within a minute, your app appears, syncs, and goes green.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment it clicks
&lt;/h2&gt;

&lt;p&gt;Edit the replica count in Git, commit, push. ArgoCD notices and scales the deployment. Then — the important test — run &lt;code&gt;kubectl scale&lt;/code&gt; &lt;em&gt;by hand&lt;/em&gt; to a different number and watch selfHeal snap it back to Git's value. That's the whole value proposition in one demo: &lt;strong&gt;production can never silently differ from the repo.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical advice from the field
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One repo per team or per app&lt;/strong&gt;, with folders per environment (&lt;code&gt;env/staging&lt;/code&gt;, &lt;code&gt;env/production&lt;/code&gt;) rather than branches per environment. Branches diverge; folders just diff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't auto-sync everything on day one.&lt;/strong&gt; Start with manual sync on production so a bad commit requires a human click; automate staging first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets don't go in Git.&lt;/strong&gt; Use Sealed Secrets, External Secrets Operator, or your cloud's secret store. Our &lt;a href="https://clinerds.com/article/kubernetes-cookbook-configmaps-secrets" rel="noopener noreferrer"&gt;ConfigMaps and Secrets guide&lt;/a&gt; covers the options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollbacks are &lt;code&gt;git revert&lt;/code&gt; + sync&lt;/strong&gt;, not archaeology. Keep commits small and you'll love this on your worst day.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A safe first week with ArgoCD
&lt;/h2&gt;

&lt;p&gt;Start with one non-critical application and leave automatic pruning off until you understand its behavior. Pruning is useful, but deleting a manifest in Git also gives ArgoCD permission to delete the corresponding cluster resource. That is exactly what you want eventually, and a painful surprise when a path or sync option is wrong.&lt;/p&gt;

&lt;p&gt;Watch the application's &lt;strong&gt;Health&lt;/strong&gt; and &lt;strong&gt;Sync&lt;/strong&gt; states separately. “Synced” means the manifests match Git; it does not prove every pod is healthy. A deployment can match the repository perfectly and still crash because its image, configuration, or database is wrong.&lt;/p&gt;

&lt;p&gt;Keep the initial admin credentials out of scripts, configure proper user access, and back up any Git repository settings that are not themselves declared elsewhere. Once the basic workflow is reliable, add staging automation, then production approvals, and only then broader pruning and self-healing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth it for small setups?
&lt;/h2&gt;

&lt;p&gt;For a single toy cluster, ArgoCD can feel like overhead. But the moment you have more than one environment or more than one person deploying, GitOps stops being ceremony and starts being the only sane way to answer "who changed what, when, and how do we undo it."&lt;/p&gt;

</description>
      <category>devops</category>
      <category>gitops</category>
      <category>argocd</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Wayland vs X11 in 2026: What's Better, What Breaks, Who Should Wait</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:06:46 +0000</pubDate>
      <link>https://dev.to/clinerds/wayland-vs-x11-in-2026-whats-better-what-breaks-who-should-wait-46i</link>
      <guid>https://dev.to/clinerds/wayland-vs-x11-in-2026-whats-better-what-breaks-who-should-wait-46i</guid>
      <description>&lt;p&gt;For thirty years, Linux desktops ran on X11. Today every major distro has moved to &lt;strong&gt;Wayland&lt;/strong&gt; — Fedora and Ubuntu default to it, and X11 is officially in maintenance mode. But the internet is full of contradictory advice: Wayland is the future, Wayland broke my setup, just use X11, X11 is dead.&lt;/p&gt;

&lt;p&gt;Here's the honest picture in 2026, including who should still avoid Wayland.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the change happened at all
&lt;/h2&gt;

&lt;p&gt;X11 was designed in 1987 for a world of dumb terminals. Its core model lets any application snoop on any other application's input and pixels — keyloggers are trivial by design. It also predates modern GPUs, mixed-DPI monitors, and touchpads, so those features were bolted on over decades.&lt;/p&gt;

&lt;p&gt;Wayland rebuilds the model: the compositor (GNOME's Mutter, KDE's KWin, Sway) owns the display, and applications get only their own pixels and events. Better security isolation, smoother frame timing, proper per-monitor scaling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually got better
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mixed-DPI setups&lt;/strong&gt; — a 4K laptop next to a 1080p monitor just works, with independent scaling per display. On X11 this was a minefield of blurry apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tearing and latency&lt;/strong&gt; — Wayland's frame presentation is synchronized; video and scrolling look visibly smoother on most hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; — an app can't silently record your keystrokes or screen anymore. Screen sharing and remote desktop now go through explicit portals that ask permission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Touchpad gestures&lt;/strong&gt; — proper three/four-finger gestures instead of X11's hacky workarounds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What still breaks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Screen sharing/recording&lt;/strong&gt; — works, but through portals (PipeWire + xdg-desktop-portal). If your app is old, sharing may show a black screen until you run it with the right flags (usually &lt;code&gt;--ozone-platform=wayland&lt;/code&gt; on Electron apps, or XWayland fallback).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NVIDIA drivers&lt;/strong&gt; — the long-standing pain point. It's &lt;em&gt;much&lt;/em&gt; better with recent proprietary drivers (explicit sync landed), but older cards and odd setups can still glitch. AMD and Intel are the safe choices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global hotkeys and automation tools&lt;/strong&gt; — apps like xdotool or global key listeners don't work across apps anymore by design. Desktop-specific replacements exist (KDE/GNOME shortcuts, &lt;code&gt;ydotool&lt;/code&gt; for lower-level needs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some remote desktop flows&lt;/strong&gt; — RDP into a Wayland session is smoother than it was, but the "log into a running graphical session" use case still trips people up. Our &lt;a href="https://clinerds.com/article/xrdp-remote-desktop-debian" rel="noopener noreferrer"&gt;XRDP guide&lt;/a&gt; covers the working setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  XWayland: the compatibility layer
&lt;/h2&gt;

&lt;p&gt;Most X11 apps run fine under Wayland via &lt;strong&gt;XWayland&lt;/strong&gt; — a compatibility server that translates the old protocol. That's why the transition has been mostly painless: your old apps still launch, they just don't get the new security and DPI benefits.&lt;/p&gt;

&lt;p&gt;Quick check of what's running natively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xlsclients        &lt;span class="c"&gt;# lists apps running through XWayland&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$XDG_SESSION_TYPE&lt;/span&gt;   &lt;span class="c"&gt;# "wayland" or "x11" — which session you're in&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A sensible compatibility check
&lt;/h2&gt;

&lt;p&gt;Before changing your daily machine, test a live USB or a second user session on the same hardware. Open a video call and share both a window and the full screen. Try your screenshot tool, password manager autotype, remote-desktop client, and any unusual global shortcuts. These small workflows are where a smooth demo can turn into a frustrating workday.&lt;/p&gt;

&lt;p&gt;If one application misbehaves, check whether it is running natively or through XWayland before blaming the whole desktop. A native build may fix scaling while an X11 fallback may temporarily fix screen capture. Keep the distribution's packages and the desktop portal services updated, and avoid copying random flags into every launcher as a permanent solution.&lt;/p&gt;

&lt;p&gt;For a production machine, make the session choice at the login screen rather than replacing lots of packages. Both sessions can often coexist, giving you an easy fallback while you test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you switch?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Switch if:&lt;/strong&gt; you're on GNOME or KDE with AMD/Intel graphics, use a laptop (especially mixed-DPI), or just want the smoothest experience. For most people in 2026, Wayland is simply better and there's no reason to stay on X11.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stay on X11 (for now) if:&lt;/strong&gt; you rely on niche X11-only tools, have an older NVIDIA card with a fragile driver situation, or run a highly customized tiling setup that you haven't ported to a Wayland compositor like Sway or Hyprland.&lt;/p&gt;

&lt;p&gt;The pragmatic answer for most people: stop choosing. Modern distros already made the choice, XWayland covers the stragglers, and the only thing you need to do is check the two or three apps that matter to you before your next upgrade.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>wayland</category>
      <category>x11</category>
      <category>desktop</category>
    </item>
    <item>
      <title>Immutable Linux Distros Explained: Silverblue, NixOS, and Why They Matter</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:06:25 +0000</pubDate>
      <link>https://dev.to/clinerds/immutable-linux-distros-explained-silverblue-nixos-and-why-they-matter-5fhj</link>
      <guid>https://dev.to/clinerds/immutable-linux-distros-explained-silverblue-nixos-and-why-they-matter-5fhj</guid>
      <description>&lt;p&gt;Every Linux user knows the failure mode: an upgrade touches one library too many, and suddenly the system that worked yesterday won't boot today. &lt;strong&gt;Immutable distros&lt;/strong&gt; attack that problem at the root — the operating system is read-only, updates arrive as complete images, and rolling back is a reboot, not a rescue mission.&lt;/p&gt;

&lt;p&gt;Once a niche idea, this model has gone mainstream: Fedora's Atomic editions (Silverblue, Kinoite), openSUSE's MicroOS, and NixOS all bet on it. Here's what it actually means and whether you should care.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "immutable" really means
&lt;/h2&gt;

&lt;p&gt;Your root filesystem is mounted read-only. You can't &lt;code&gt;apt install&lt;/code&gt; into the base system because there is no base system to mutate — the OS is a versioned image. Apps arrive via Flatpak, containers, or toolbox environments that sit &lt;em&gt;on top&lt;/em&gt; of the untouched base.&lt;/p&gt;

&lt;p&gt;Upgrades replace the whole image: the new version stages alongside the current one, you reboot into it, and if anything breaks you reboot into the previous image. No half-applied updates, no dependency hell, no "my system is 60% upgraded."&lt;/p&gt;

&lt;h2&gt;
  
  
  The main options in 2026
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fedora Silverblue / Kinoite&lt;/strong&gt; — the gentle on-ramp. Full Fedora underneath (Silverblue = GNOME, Kinoite = KDE), Flatpak for apps, &lt;code&gt;rpm-ostree&lt;/code&gt; for system images. If you already know Fedora, the learning curve is mostly Flatpak and the &lt;code&gt;toolbox&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NixOS&lt;/strong&gt; — the other end of the spectrum. Your &lt;em&gt;entire system&lt;/em&gt; is declared in a single &lt;code&gt;configuration.nix&lt;/code&gt; file: packages, users, services, kernel options. Rebuilds are atomic and every previous generation is bootable. It's the most powerful model and the steepest learning curve — you're learning a new language, not just a new distro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;openSUSE MicroOS&lt;/strong&gt; — transactional updates with Btrfs snapshots, strong in server and edge deployments. Less desktop-polished than Fedora's offering but very solid.&lt;/p&gt;

&lt;h2&gt;
  
  
  A day in the life
&lt;/h2&gt;

&lt;p&gt;On a traditional distro you install apps into the system. On Silverblue the workflow looks different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flatpak &lt;span class="nb"&gt;install &lt;/span&gt;flathub org.mozilla.firefox   &lt;span class="c"&gt;# desktop apps: Flatpak&lt;/span&gt;
toolbox create dev                              &lt;span class="c"&gt;# a mutable Fedora container&lt;/span&gt;
toolbox enter dev
&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;gcc vim                        &lt;span class="c"&gt;# dev tools inside the container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;System-level packages you truly need on the host get layered with &lt;code&gt;rpm-ostree install htop&lt;/code&gt; (which creates a new image and needs a reboot). On NixOS you instead add the package to &lt;code&gt;configuration.nix&lt;/code&gt; and run &lt;code&gt;sudo nixos-rebuild switch&lt;/code&gt; — the system converges to the declared state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is actually for
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Great fit:&lt;/strong&gt; laptops you depend on daily (a bad update can never brick your morning), homelab servers you want reproducible, and anyone who has ever thought "I wish I could just rebuild this machine from a file."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Poor fit:&lt;/strong&gt; heavy kernel-module tinkerers, people who constantly install random &lt;code&gt;.deb&lt;/code&gt;/&lt;code&gt;.rpm&lt;/code&gt; packages from the internet, and workflows that demand writing all over the host filesystem. You &lt;em&gt;can&lt;/em&gt; force these, but you'll fight the design instead of enjoying it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updates, rollbacks, and backups
&lt;/h2&gt;

&lt;p&gt;Atomic updates are not the same as automatic backups. The image can protect your operating system from a half-finished upgrade, but it cannot recover documents you deleted or a disk that failed. Keep a separate backup routine for &lt;code&gt;/home&lt;/code&gt; and any application data.&lt;/p&gt;

&lt;p&gt;On an rpm-ostree system, inspect the available deployments with &lt;code&gt;rpm-ostree status&lt;/code&gt;. If the newest deployment has a problem, &lt;code&gt;sudo rpm-ostree rollback&lt;/code&gt; selects the previous one for the next boot. On NixOS, previous generations remain available from the boot menu. Test that rollback path before you depend on it, and read the update notes for major releases.&lt;/p&gt;

&lt;p&gt;Also remember that “immutable” does not mean the whole machine is frozen. Persistent directories such as &lt;code&gt;/etc&lt;/code&gt;, &lt;code&gt;/var&lt;/code&gt;, and your home directory still change. NixOS takes a declarative approach rather than simply making every root filesystem read-only, but the practical goal is similar: reproducible system state and safer transitions between versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest trade-off
&lt;/h2&gt;

&lt;p&gt;You give up the casual flexibility of "just install it into the system." In return you get an OS that is nearly unbreakable, trivially reproducible, and boring in the best way. For a growing number of people — especially after one too many upgrade disasters — that trade is an easy yes.&lt;/p&gt;

&lt;p&gt;If you're curious, try Silverblue in a VM for a week. If the Flatpak + toolbox workflow clicks, you'll understand why immutable desktops stopped being a niche experiment.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>immutable</category>
      <category>nixos</category>
      <category>fedora</category>
    </item>
  </channel>
</rss>
