<?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: Max</title>
    <description>The latest articles on DEV Community by Max (@orthogonalinfo).</description>
    <link>https://dev.to/orthogonalinfo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3847175%2F78878eb1-022c-4880-ba72-cde851bc87d8.png</url>
      <title>DEV Community: Max</title>
      <link>https://dev.to/orthogonalinfo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/orthogonalinfo"/>
    <language>en</language>
    <item>
      <title>Build an Unusual Options Activity Scanner With Python and Free Data</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 15 Apr 2026 18:12:49 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/build-an-unusual-options-activity-scanner-with-python-and-free-data-kka</link>
      <guid>https://dev.to/orthogonalinfo/build-an-unusual-options-activity-scanner-with-python-and-free-data-kka</guid>
      <description>&lt;p&gt;Last month I noticed something odd: SMCI options volume spiked to 8x its 20-day average on a random Tuesday afternoon. No news. No earnings. Three days later, the stock jumped 14% on a surprise partnership announcement. Someone knew.&lt;/p&gt;

&lt;p&gt;Unusual options activity (UOA) — when volume on a specific contract explodes beyond normal levels — is one of the most reliable signals that informed money is positioning. Services like Unusual Whales and Cheddar Flow charge $40-80/month to show you this data. I built my own scanner for free in about 200 lines of Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Counts as "Unusual"
&lt;/h2&gt;

&lt;p&gt;Before writing code, you need a working definition. I use three filters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Volume/Open Interest ratio &amp;gt; 3.0&lt;/strong&gt; — When daily volume on a contract is 3x or more the existing open interest, that’s new money entering, not existing positions rolling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Premium &amp;gt; $25,000&lt;/strong&gt; — Filters out noise. A retail trader buying 5 contracts of a cheap OTM option isn’t a signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days to expiration between 7-90&lt;/strong&gt; — Too short means gamma scalping. Too long means it’s likely a hedge, not a directional bet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren’t perfect — no filter is. But they eliminate about 95% of the noise and leave you with 10-30 actionable alerts per day instead of thousands.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Problem (and Three Free Solutions)
&lt;/h2&gt;

&lt;p&gt;Options data is expensive. Real-time feeds from OPRA cost thousands per month. But for a daily scanner that runs after market close, you don’t need real-time. Here are three approaches I tested:&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Tradier Sandbox API (My Pick)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://tradier.com" rel="noopener noreferrer"&gt;Tradier&lt;/a&gt; offers a free sandbox API that includes delayed options chains with volume and open interest. The delay is 15 minutes, which is fine for an end-of-day scanner. Rate limit: 120 requests/minute on the free tier.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;TRADIER_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_SANDBOX_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Free at developer.tradier.com
&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://sandbox.tradier.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TRADIER_TOKEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_options_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# First get expiration dates
&lt;/span&gt;    &lt;span class="n"&gt;exp_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/markets/options/expirations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exp_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expirations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;all_contracts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;exp_date&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;  &lt;span class="c1"&gt;# Next 6 expirations
&lt;/span&gt;        &lt;span class="n"&gt;chain_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/markets/options/chains&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expiration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exp_date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;option&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
        &lt;span class="n"&gt;all_contracts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;all_contracts&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each contract in the response includes &lt;code&gt;volume&lt;/code&gt;, &lt;code&gt;open_interest&lt;/code&gt;, &lt;code&gt;last&lt;/code&gt;, and &lt;code&gt;option_type&lt;/code&gt;. That’s everything you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Yahoo Finance (yfinance)
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;yfinance&lt;/code&gt; library pulls options data directly. No API key needed. The catch: it’s slow (one request per ticker) and Yahoo occasionally rate-limits aggressive scraping.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yfinance&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;yf&lt;/span&gt;

&lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Ticker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;exp_date&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exp_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;  &lt;span class="c1"&gt;# DataFrame with volume, openInterest, etc.
&lt;/span&gt;    &lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used this initially but switched to Tradier. Yahoo’s data occasionally has gaps — missing volume on contracts that clearly traded — and the rate limiting makes scanning 100+ symbols painful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Polygon.io Free Tier
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://polygon.io" rel="noopener noreferrer"&gt;Polygon.io&lt;/a&gt; gives you 5 API calls/minute on the free tier. That’s rough for options scanning since you need one call per expiration per symbol. I’d only recommend this if you’re scanning fewer than 20 symbols.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scanner: 200 Lines That Actually Work
&lt;/h2&gt;

&lt;p&gt;Here’s the core logic. I run this daily at 4:30 PM ET via cron.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scan_unusual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contracts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;min_vol_oi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;min_premium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_dte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Filter options contracts for unusual activity.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;unusual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;contracts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;oi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open_interest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;last_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="c1"&gt;# Skip dead contracts
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;last_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="c1"&gt;# Calculate days to expiration
&lt;/span&gt;        &lt;span class="n"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expiration_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;dte&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dte&lt;/span&gt;  &lt;span class="n"&gt;max_dte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="c1"&gt;# Volume/OI ratio (handle zero OI)
&lt;/span&gt;        &lt;span class="n"&gt;vol_oi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;vol_oi&lt;/span&gt;  &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;by_symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call_premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;put_premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call_premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;put_premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;by_symbol&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]][&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;by_symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call_premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;put_premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;bull_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call_premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
            &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bullish_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bull_pct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_premium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Delivery.&lt;/strong&gt; I push the top alerts to a Telegram channel using a bot. You could also use &lt;a href="https://ntfy.sh" rel="noopener noreferrer"&gt;ntfy.sh&lt;/a&gt; (free, self-hostable) or plain email via &lt;code&gt;smtplib&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned Running This for 6 Months
&lt;/h2&gt;

&lt;p&gt;A few hard-earned observations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UOA predicts direction roughly 60% of the time.&lt;/strong&gt; That’s better than a coin flip, but it’s not magic. Don’t bet the farm on any single alert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sector clustering matters more than individual signals.&lt;/strong&gt; When you see unusual call activity across 5 semiconductor names on the same day, that’s more meaningful than a single NVDA spike.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Earnings week is noise.&lt;/strong&gt; I exclude any ticker with earnings within 5 trading days. The UOA around earnings is mostly people buying lottery tickets, not informed positioning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Friday afternoon sweeps are the best signals.&lt;/strong&gt; Big money placing bets late Friday when retail has checked out? That often moves Monday-Tuesday.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Full Setup on a Raspberry Pi
&lt;/h2&gt;

&lt;p&gt;My scanner runs on a &lt;a href="https://www.amazon.com/Raspberry-Model-2GB-Quad-Core-Bluetooth/dp/B0CPWH8FL9?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;Raspberry Pi 5&lt;/a&gt; that also handles my other homelab scripts. Total resource usage: ~40MB RAM, finishes in under 10 minutes. Cron triggers it at 4:30 PM ET, and I get a Telegram notification with the day’s unusual activity by 4:40 PM.&lt;/p&gt;

&lt;p&gt;If you want a more portable development environment, a &lt;a href="https://www.amazon.com/Samsung-T7-Portable-External-MU-PC1T0T/dp/B0874XN4D8?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;Samsung T7 portable SSD&lt;/a&gt; makes it easy to carry your full dev setup between machines — I keep my Python environments and data on one so I can plug into any workstation.&lt;/p&gt;

&lt;p&gt;For going deeper on the quantitative side, &lt;a href="https://www.amazon.com/Python-Finance-Mastering-Data-Driven/dp/1492024333?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;Python for Finance by Yves Hilpisch&lt;/a&gt; is the best resource I’ve found for turning signals like these into a backtestable strategy. It covers everything from data handling to options pricing models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should You Actually Trade on UOA?
&lt;/h2&gt;

&lt;p&gt;Honestly? Maybe. I use it as one input alongside technicals and macro. The signals are real — informed money does move through the options market before news drops. But “informed” doesn’t mean “always right,” and options flow data is increasingly gamed by sophisticated players who know retail is watching.&lt;/p&gt;

&lt;p&gt;The real value for me has been understanding market sentiment. When I see aggressive call buying across financials before an FOMC meeting, that tells me something about positioning — even if I don’t trade it directly.&lt;/p&gt;

&lt;p&gt;If you want daily market intelligence covering signals like these, I run a free Telegram channel: &lt;a href="https://t.me/alphasignal822" rel="noopener noreferrer"&gt;Join Alpha Signal&lt;/a&gt; for free market analysis, sector rotation tracking, and macro breakdowns.&lt;/p&gt;

&lt;p&gt;The full scanner code is about 200 lines. I’m considering open-sourcing it — if there’s interest, I’ll throw it on GitHub. For now, the snippets above give you everything you need to build your own.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Related: &lt;a href="https://orthogonal.info/track-congress-trades-with-python-free-sec-data/" rel="noopener noreferrer"&gt;Track Congress Trades with Python&lt;/a&gt; | &lt;a href="https://orthogonal.info/insider-trading-detector-with-python-free-sec-data/" rel="noopener noreferrer"&gt;Insider Trading Detector with Python&lt;/a&gt; | &lt;a href="https://orthogonal.info/algorithmic-trading-engineering-guide/" rel="noopener noreferrer"&gt;Algorithmic Trading for Engineers&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure: Amazon links above are affiliate links.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>trading</category>
      <category>finance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Full Stack Monitoring: A Security-First Approach</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 15 Apr 2026 16:46:34 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/full-stack-monitoring-a-security-first-approach-2j6o</link>
      <guid>https://dev.to/orthogonalinfo/full-stack-monitoring-a-security-first-approach-2j6o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Full stack monitoring is essential for modern architectures, encompassing infrastructure, applications, and user experience. A security-first approach ensures that monitoring not only detects performance issues but also safeguards against threats. By integrating DevSecOps principles, you can create a scalable, resilient, and secure monitoring strategy tailored for Kubernetes environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Answer:&lt;/strong&gt; Full stack monitoring is the practice of observing every layer of your system, from infrastructure to user experience, with a focus on performance and security. It’s critical for detecting issues early and maintaining a secure, reliable environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction to Full Stack Monitoring
&lt;/h2&gt;

&lt;p&gt;Imagine your application stack as a high-performance race car. The engine (infrastructure), the driver (application), and the tires (user experience) all need to work in harmony for the car to perform well. Now imagine trying to diagnose a problem during a race without any telemetry—no speedometer, no engine diagnostics, no tire pressure readings. That’s what running a modern system without full stack monitoring feels like.&lt;/p&gt;

&lt;p&gt;Full stack monitoring is the practice of observing every layer of your system, from the underlying infrastructure to the end-user experience. It’s not just about ensuring uptime; it’s about understanding how each component interacts and identifying issues before they escalate. In today’s threat landscape, a security-first approach to monitoring is non-negotiable. Attackers don’t just exploit vulnerabilities—they exploit blind spots. (For network-layer visibility, see &lt;a href="https://orthogonal.info/mastering-kubernetes-security-network-policies-service-mesh/" rel="noopener noreferrer"&gt;Kubernetes Network Policies and Service Mesh Security&lt;/a&gt;.) Monitoring every layer ensures you’re not flying blind.&lt;/p&gt;

&lt;p&gt;Key components of full stack monitoring include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure Monitoring:&lt;/strong&gt; Observing servers, networks, and cloud resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Monitoring:&lt;/strong&gt; Tracking application performance, APIs, and microservices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Experience Monitoring:&lt;/strong&gt; Measuring how end-users interact with your application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here’s the kicker: monitoring without a security-first mindset is like locking your front door while leaving the windows wide open. Let’s explore why security-first monitoring is critical and how it integrates seamlessly with Kubernetes and DevSecOps principles.&lt;/p&gt;

&lt;p&gt;Full stack monitoring also provides the foundation for proactive system management. By collecting and analyzing data across all layers, teams can identify trends, predict potential failures, and optimize performance. For example, if your application experiences a sudden spike in database queries, monitoring can help pinpoint whether the issue lies in the application code, database configuration, or user behavior.&lt;/p&gt;

&lt;p&gt;Additionally, full stack monitoring is invaluable for compliance. Many industries, such as finance and healthcare, require detailed logs and metrics to demonstrate adherence to regulations. A robust monitoring strategy ensures you have the necessary data to pass audits and maintain trust with stakeholders.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Pro Tip:&lt;/strong&gt; Start by mapping out your entire stack and identifying the most critical components to monitor. This will help you prioritize resources and avoid being overwhelmed by data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s a simple example of setting up a basic monitoring script using Python to track CPU and memory usage:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;monitor_system&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cpu_usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;psutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpu_percent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;memory_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;psutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;virtual_memory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CPU Usage: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cpu_usage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Memory Usage: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;memory_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;percent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;monitor_system&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script provides a starting point for understanding system resource usage, which can be extended to include additional metrics or integrated with a larger monitoring framework.&lt;/p&gt;

&lt;p&gt;Another practical example is using a cloud-based monitoring service like AWS CloudWatch or Google Cloud Operations Suite. These tools provide built-in integrations with your cloud infrastructure, making it easier to monitor resources like virtual machines, databases, and storage buckets. For instance, you can set up alarms in AWS CloudWatch to notify your team when CPU utilization exceeds a certain threshold, helping you respond to performance issues before they impact users.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Common Pitfall:&lt;/strong&gt; Avoid overloading your monitoring system with unnecessary metrics. Too much data can obscure critical insights and overwhelm your team.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To address edge cases, consider scenarios where your monitoring tools fail or produce incomplete data. For example, if your monitoring system relies on a single server and that server crashes, you lose visibility into your stack. Implementing redundancy and failover mechanisms for your monitoring infrastructure ensures continuous observability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Role of Full Stack Monitoring in Kubernetes
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;If you're hardening your cluster alongside monitoring, check out the &lt;a href="https://orthogonal.info/kubernetes-security-checklist-for-production-2026/" rel="noopener noreferrer"&gt;Kubernetes Security Checklist for Production&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes is a game-changer for modern application deployment, but it’s also a monitoring nightmare. Pods come and go, nodes scale dynamically, and workloads are distributed across clusters. Traditional monitoring tools struggle to keep up with this level of complexity.&lt;/p&gt;

&lt;p&gt;Full stack monitoring in Kubernetes involves tracking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cluster Health:&lt;/strong&gt; Monitoring nodes, pods, and resource utilization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Performance:&lt;/strong&gt; Observing how services interact and identifying bottlenecks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Events:&lt;/strong&gt; Detecting unauthorized access, privilege escalations, and misconfigurations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tools like &lt;strong&gt;Prometheus&lt;/strong&gt; and &lt;strong&gt;Grafana&lt;/strong&gt; are staples for Kubernetes monitoring. Prometheus collects metrics from Kubernetes components, while Grafana visualizes them in dashboards. But these tools are just the start. For a security-first approach, you’ll want to integrate solutions like &lt;strong&gt;Falco&lt;/strong&gt; for runtime security and &lt;strong&gt;Open Policy Agent (OPA)&lt;/strong&gt; for policy enforcement.&lt;/p&gt;

&lt;p&gt;In a real-world scenario, consider a Kubernetes cluster running a microservices-based e-commerce application. Without proper monitoring, a sudden increase in traffic could overwhelm the payment service, causing delays or failures. By using Prometheus to monitor pod resource usage and Grafana to visualize trends, you can identify the issue and scale the affected service before it impacts users.&lt;/p&gt;

&lt;p&gt;Another critical aspect is monitoring Kubernetes API server logs. These logs can reveal unauthorized access attempts or misconfigured RBAC (Role-Based Access Control) policies. For example, if a developer accidentally grants admin privileges to a service account, monitoring tools can alert you to the potential security risk.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Security Note:&lt;/strong&gt; The default configurations of many Kubernetes monitoring tools are not secure. Always enable authentication and encryption for Prometheus endpoints and Grafana dashboards.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s an example of setting up Prometheus to scrape metrics securely:&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;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15s&lt;/span&gt;
  &lt;span class="na"&gt;evaluation_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15s&lt;/span&gt;

&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;kubernetes-nodes'&lt;/span&gt;
    &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https&lt;/span&gt;
    &lt;span class="na"&gt;tls_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ca_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/prometheus/ssl/ca.crt&lt;/span&gt;
      &lt;span class="na"&gt;cert_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/prometheus/ssl/prometheus.crt&lt;/span&gt;
      &lt;span class="na"&gt;key_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/prometheus/ssl/prometheus.key&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes_sd_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration ensures that Prometheus communicates securely with Kubernetes nodes using TLS.&lt;/p&gt;

&lt;p&gt;When implementing monitoring in Kubernetes, it’s essential to account for the ephemeral nature of containers. Logs and metrics should be centralized to prevent data loss when pods are terminated. Tools like Fluentd and Elasticsearch can help aggregate logs, while Prometheus handles metrics collection.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Pro Tip:&lt;/strong&gt; Use Kubernetes namespaces to organize monitoring resources. For example, create a dedicated namespace for Prometheus, Grafana, and other observability tools to simplify management.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To further enhance security, consider using network policies to restrict communication between monitoring tools and other components. For example, you can use Calico or Cilium to define policies that allow Prometheus to scrape metrics only from specific namespaces or pods.&lt;/p&gt;

&lt;h2&gt;
  
  
  DevSecOps and Full Stack Monitoring: A Perfect Match
&lt;/h2&gt;

&lt;p&gt;DevSecOps is the philosophy of integrating security into every phase of the development lifecycle. When applied to monitoring, it means embedding security checks and alerts into your observability stack. This approach not only improves security but also enhances reliability and performance.&lt;/p&gt;

&lt;p&gt;Here’s how DevSecOps principles enhance full stack monitoring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shift Left:&lt;/strong&gt; Monitor security metrics during development, not just in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation:&lt;/strong&gt; Use CI/CD pipelines to deploy and update monitoring configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaboration:&lt;/strong&gt; Share monitoring insights across development, operations, and security teams.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, integrating &lt;strong&gt;SonarQube&lt;/strong&gt; into your CI/CD pipeline can help identify code vulnerabilities early. Similarly, tools like &lt;strong&gt;Datadog&lt;/strong&gt; and &lt;strong&gt;New Relic&lt;/strong&gt; can provide real-time insights into application performance and security.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Pro Tip:&lt;/strong&gt; Use Infrastructure as Code (IaC) tools like Terraform to manage your monitoring stack. This ensures consistency across environments and makes it easier to audit changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s an example of using Terraform to deploy a Prometheus and Grafana stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;helm_release&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prometheus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prometheus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;chart&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prometheus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://prometheus-community.github.io/helm-charts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;monitoring&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;helm_release&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grafana&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grafana&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;chart&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grafana&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://grafana.github.io/helm-charts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;monitoring&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Terraform configuration deploys Prometheus and Grafana using Helm charts, ensuring a consistent setup across environments.&lt;/p&gt;

&lt;p&gt;Another key aspect of DevSecOps is integrating security scanning into your monitoring pipeline. Tools like Aqua Security and Trivy can scan container images for vulnerabilities, while Falco can detect runtime anomalies. For example, if a container starts running an unexpected process, Falco can trigger an alert and even terminate the container to prevent further damage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🔒 Security Note:&lt;/strong&gt; Always use signed container images from trusted sources to minimize the risk of deploying compromised software.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Advanced Monitoring Techniques
&lt;/h2&gt;

&lt;p&gt;While traditional monitoring focuses on metrics and logs, advanced techniques like distributed tracing and anomaly detection can take your observability to the next level. Distributed tracing tools such as Jaeger and Zipkin allow you to track requests as they flow through microservices, providing insights into latency and bottlenecks.&lt;/p&gt;

&lt;p&gt;Anomaly detection, powered by machine learning, can identify unusual patterns in your metrics. For example, if your application suddenly experiences a spike in error rates during off-peak hours, anomaly detection tools can flag this as a potential issue. Tools like Elastic APM and Dynatrace provide built-in anomaly detection capabilities. For a deeper dive into open-source security monitoring, see our guide on &lt;a href="https://orthogonal.info/enterprise-security-at-home-wazuh-suricata-setup/" rel="noopener noreferrer"&gt;setting up Wazuh and Suricata for enterprise-grade detection&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Pro Tip:&lt;/strong&gt; Combine distributed tracing with metrics and logs for a comprehensive observability strategy. This triad ensures you capture every aspect of your system’s behavior.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s an example of configuring Jaeger for distributed tracing in Kubernetes:&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;v1&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;ConfigMap&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;jaeger-config&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;monitoring&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;config.yaml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;collector:&lt;/span&gt;
      &lt;span class="s"&gt;zipkin:&lt;/span&gt;
        &lt;span class="s"&gt;http-port: 9411&lt;/span&gt;
    &lt;span class="s"&gt;storage:&lt;/span&gt;
      &lt;span class="s"&gt;type: memory&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration sets up Jaeger to collect traces and store them in memory, suitable for development environments.&lt;/p&gt;

&lt;p&gt;Advanced monitoring also includes synthetic monitoring, where simulated user interactions are used to test application performance. For example, you can use tools like Selenium or Puppeteer to simulate user actions such as logging in or making a purchase. These tests can be scheduled to run periodically, ensuring your application remains functional under various conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Trends in Full Stack Monitoring
&lt;/h2&gt;

&lt;p&gt;As technology evolves, so does the field of monitoring. Emerging trends include the use of AI and predictive analytics to anticipate issues before they occur. For example, AI-driven monitoring tools can analyze historical data to predict when a server might fail or when traffic spikes are likely to occur.&lt;/p&gt;

&lt;p&gt;Another trend is the integration of observability with chaos engineering. Tools like Gremlin allow you to simulate failures in your system, testing its resilience and ensuring your monitoring tools can detect and respond to these events effectively.&lt;/p&gt;

&lt;p&gt;Finally, edge computing is reshaping monitoring strategies. With data being processed closer to users, monitoring tools must adapt to decentralized architectures. Tools like Prometheus and Grafana are evolving to support edge deployments, ensuring visibility across distributed systems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Pro Tip:&lt;/strong&gt; Stay ahead of the curve by experimenting with AI-driven monitoring tools and chaos engineering practices. These approaches can significantly enhance your system’s resilience and observability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🛠️ Recommended Resources:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tools and books mentioned in (or relevant to) this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/dp/1617297615?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;Kubernetes in Action, 2nd Edition&lt;/a&gt; — The definitive guide to deploying and managing K8s in production ($45-55)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/dp/1492083658?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;Learning Helm&lt;/a&gt; — Managing apps on Kubernetes with the Helm package manager ($35-45)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/dp/1492081736?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;Hacking Kubernetes&lt;/a&gt; — Threat-driven analysis and defense of K8s clusters ($40-50)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/dp/1617297275?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;GitOps and Kubernetes&lt;/a&gt; — Continuous deployment with Argo CD, Jenkins X, and Flux ($40-50)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is full stack monitoring?
&lt;/h3&gt;

&lt;p&gt;Full stack monitoring is the practice of observing every layer of a system, including infrastructure, applications, and user experience. It ensures optimal performance and security by identifying issues early and understanding how different components interact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is a security-first approach important in monitoring?
&lt;/h3&gt;

&lt;p&gt;A security-first approach ensures that monitoring not only detects performance issues but also safeguards against potential threats. Attackers often exploit blind spots, so monitoring every layer of the system helps prevent vulnerabilities from being overlooked.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the key components of full stack monitoring?
&lt;/h3&gt;

&lt;p&gt;The key components include infrastructure monitoring (servers, networks, cloud resources), application monitoring (performance, APIs, microservices), and user experience monitoring (how end-users interact with the application).&lt;/p&gt;

&lt;h3&gt;
  
  
  How does full stack monitoring integrate with DevSecOps principles?
&lt;/h3&gt;

&lt;p&gt;By integrating DevSecOps principles, full stack monitoring becomes a proactive tool for security and performance. It ensures that monitoring strategies are scalable, resilient, and tailored for environments like Kubernetes, aligning development, security, and operations teams.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>TrueNAS Setup Guide: Enterprise Security at Home</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Tue, 14 Apr 2026 16:47:13 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/truenas-setup-guide-enterprise-security-at-home-501</link>
      <guid>https://dev.to/orthogonalinfo/truenas-setup-guide-enterprise-security-at-home-501</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; TrueNAS is a powerful storage solution for homelabs, offering enterprise-grade features like ZFS, encryption, and snapshots. This guide walks you through setting up TrueNAS securely, from hardware selection to implementing firewalls and VPNs. By following these steps, you’ll ensure your data is safe, accessible, and future-proof.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick Answer:&lt;/strong&gt; TrueNAS is the best choice for secure, scalable storage in a homelab. With proper setup, including encryption, access controls, and regular updates, you can achieve enterprise-level security at home.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction to TrueNAS and Homelab Security
&lt;/h2&gt;

&lt;p&gt;It started with a simple question: “Why am I trusting a random cloud provider with my personal data?” That thought led me down the rabbit hole of homelab storage solutions, and eventually to TrueNAS. TrueNAS, with its ZFS foundation, enterprise-grade features, and open-source roots, quickly became my go-to choice for secure, reliable storage.&lt;/p&gt;

&lt;p&gt;TrueNAS is more than just a NAS (Network Attached Storage); it’s a full-fledged storage operating system. Whether you’re running TrueNAS CORE or SCALE, you get features like snapshots, replication, and encryption—tools you’d typically find in enterprise environments. But here’s the catch: with great power comes great responsibility. Misconfiguring TrueNAS can leave your data vulnerable to attacks or corruption.&lt;/p&gt;

&lt;p&gt;In this guide, I’ll show you how to set up TrueNAS in your homelab with a security-first mindset. We’ll cover everything from hardware selection to implementing firewalls and VPNs. By the end, you’ll have a robust, secure storage solution that rivals enterprise setups—scaled down for personal use.&lt;/p&gt;

&lt;p&gt;Homelab security is often overlooked, but it’s just as critical as the security of enterprise systems. Cyberattacks, ransomware, and data breaches are no longer limited to large corporations. Even personal setups can be targeted, especially if they’re improperly configured or exposed to the internet. TrueNAS provides a solid foundation for securing your data, but it’s up to you to implement best practices and maintain vigilance.&lt;/p&gt;

&lt;p&gt;One of the key benefits of TrueNAS is its ability to scale with your needs. Whether you’re a hobbyist storing family photos or a developer managing terabytes of project data, TrueNAS can adapt to your requirements. However, scaling also introduces complexity, which makes proper planning and configuration even more important. This guide will help you navigate these challenges and build a system that’s both secure and scalable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning Your TrueNAS Setup
&lt;/h2&gt;

&lt;p&gt;Before diving into installation, you need to plan your setup. A well-thought-out plan will save you headaches later, especially when it comes to scaling or troubleshooting. Here’s what you need to consider:&lt;/p&gt;

&lt;h3&gt;
  
  
  Hardware Requirements and Recommendations
&lt;/h3&gt;

&lt;p&gt;TrueNAS can run on a variety of hardware, but not all setups are created equal. For 2025 and beyond, here are my recommendations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPU:&lt;/strong&gt; At least a quad-core processor. Intel Xeon or AMD Ryzen are excellent choices for ECC memory support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAM:&lt;/strong&gt; Minimum 16GB, but 32GB+ is recommended for ZFS deduplication and caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; Use enterprise-grade HDDs (e.g., Seagate IronWolf Pro or WD Red Pro) for reliability. SSDs are great for caching or fast datasets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NIC:&lt;/strong&gt; A 1GbE NIC is sufficient for most homelabs, but consider 10GbE if you’re dealing with large data transfers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Pro Tip: Always use ECC (Error-Correcting Code) memory if your motherboard supports it. ZFS relies heavily on RAM, and ECC ensures data integrity by preventing bit-flipping errors.&lt;/p&gt;

&lt;p&gt;When selecting hardware, consider future-proofing your setup. For example, if you anticipate needing more storage in the future, choose a motherboard with additional SATA or NVMe slots. Similarly, if you plan to run virtual machines or containers on TrueNAS SCALE, invest in a CPU with higher core counts and better multi-threading capabilities.&lt;/p&gt;

&lt;p&gt;Another important consideration is power consumption. Homelabs often run 24/7, so energy-efficient components can save you money in the long run. Look for CPUs and drives with low power draw, and consider using a power-efficient PSU (Power Supply Unit) with an 80 Plus Gold or Platinum rating.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing the Right TrueNAS Version
&lt;/h3&gt;

&lt;p&gt;TrueNAS comes in two flavors: CORE and SCALE. Here’s a quick comparison to help you decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TrueNAS CORE:&lt;/strong&gt; Based on FreeBSD, it’s stable and battle-tested. Ideal for traditional NAS use cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TrueNAS SCALE:&lt;/strong&gt; Linux-based with Kubernetes support. Perfect for running containers and virtual machines alongside your storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re planning to integrate your NAS with Docker or Kubernetes, go with SCALE. Otherwise, CORE is a solid choice for pure storage needs.&lt;/p&gt;

&lt;p&gt;💡 Pro Tip: If you’re unsure which version to choose, start with TrueNAS CORE. You can always migrate to SCALE later if your needs evolve. The TrueNAS community forums are also a great resource for advice and troubleshooting.&lt;/p&gt;

&lt;p&gt;It’s worth noting that TrueNAS SCALE is relatively new compared to CORE, so some features may still be in development. If you require cutting-edge functionality like container orchestration, SCALE is the way to go. However, if you prioritize stability and a proven track record, CORE is the safer bet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network Considerations
&lt;/h3&gt;

&lt;p&gt;Your network setup plays a critical role in both performance and security. Here are some best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use VLANs to segment your NAS traffic from other devices.&lt;/li&gt;
&lt;li&gt;Set up a dedicated management interface for TrueNAS.&lt;/li&gt;
&lt;li&gt;Enable jumbo frames if your network supports it for better performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Security Note:&lt;/strong&gt; Never expose your TrueNAS web interface directly to the internet. Always use a VPN or reverse proxy with authentication.&lt;/p&gt;

&lt;p&gt;For homelabs with multiple devices, consider using a managed switch to create VLANs (Virtual Local Area Networks). VLANs allow you to isolate your NAS from less secure devices, such as IoT gadgets, reducing the risk of lateral movement in case of a breach. For example, you could place your NAS on VLAN 10 and your IoT devices on VLAN 20, ensuring they can’t communicate directly.&lt;/p&gt;

&lt;p&gt;Another important aspect of network planning is IP addressing. Assign a static IP to your TrueNAS server to avoid issues with DHCP leases expiring or changing. This is especially important if you plan to access your NAS remotely or integrate it with other services like Proxmox or Plex.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation and Initial Configuration
&lt;/h2&gt;

&lt;p&gt;With your hardware and network plan in place, it’s time to install TrueNAS. Here’s a step-by-step guide:&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing TrueNAS
&lt;/h3&gt;

&lt;p&gt;Download the latest ISO from the official TrueNAS website. Use a tool like Rufus to create a bootable USB drive. Boot your server from the USB and follow the installation wizard. Choose the boot drive carefully—it should be a small SSD or USB stick, separate from your storage drives.&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="c"&gt;# Example: Creating a bootable USB on Linux&lt;/span&gt;
&lt;span class="nb"&gt;sudo dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;truenas.iso &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/sdX &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4M &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;progress

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During installation, you’ll be prompted to configure basic settings like timezone and network interfaces. Take your time to review these options, as they can impact your system’s performance and accessibility. For example, if you’re using multiple NICs, ensure the correct one is selected for management purposes.&lt;/p&gt;

&lt;p&gt;💡 Pro Tip: If you’re using a USB stick as your boot drive, consider creating a backup of the installation. USB drives can fail over time, so having a backup will save you from having to reinstall and reconfigure everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Storage Pools and Datasets
&lt;/h3&gt;

&lt;p&gt;Once installed, log in to the TrueNAS web interface. The first step is setting up your storage pool. Use RAID-Z for redundancy and performance. For example, RAID-Z2 offers a good balance of fault tolerance and usable space.&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="c"&gt;# Example: Creating a ZFS pool via CLI (if needed)&lt;/span&gt;
zpool create &lt;span class="nt"&gt;-f&lt;/span&gt; mypool raidz2 /dev/sd[b-e]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create datasets for organizing your data. Datasets allow you to apply specific settings like compression, quotas, and permissions at a granular level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Pro Tip:&lt;/strong&gt; Enable compression (e.g., LZ4) on all datasets. It improves performance and saves space without noticeable overhead.&lt;/p&gt;

&lt;p&gt;When setting up datasets, think about how you’ll use your storage. For example, you might create separate datasets for media, backups, and personal files. This not only helps with organization but also allows you to apply different settings to each dataset. For instance, you could enable deduplication for backups but disable it for media files to save on system resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up User Accounts
&lt;/h3&gt;

&lt;p&gt;TrueNAS supports multiple user accounts, each with specific permissions. Avoid using the root account for daily tasks. Instead, create individual accounts for each user and assign them to groups for easier management.&lt;/p&gt;

&lt;p&gt;To enhance security, use strong, unique passwords for each account. If you’re managing multiple users, consider enabling two-factor authentication (2FA) for added protection. TrueNAS also supports SSH key-based authentication, which is more secure than password-based logins.&lt;/p&gt;

&lt;p&gt;💡 Pro Tip: Use groups to manage permissions more efficiently. For example, create a “Media” group for users who need access to your media dataset, and assign permissions at the group level instead of individually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Enterprise-Grade Security Practices
&lt;/h2&gt;

&lt;p&gt;Now that your TrueNAS is up and running, let’s secure it. These steps will help you implement enterprise-grade security practices:&lt;/p&gt;

&lt;h3&gt;
  
  
  Enabling Encryption
&lt;/h3&gt;

&lt;p&gt;TrueNAS supports encryption at the dataset level. Enable it during dataset creation and store the encryption keys securely. For added security, use a hardware security module (HSM) or a password-protected key file.&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="c"&gt;# Example: Encrypting a dataset via CLI&lt;/span&gt;
zfs create &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;encryption&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;keyformat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;passphrase mypool/securedata

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Encryption is a critical feature for protecting sensitive data, but it’s only effective if the keys are managed properly. Avoid storing encryption keys on the same device as your TrueNAS server. Instead, use a secure external device or a dedicated key management system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Pro Tip:&lt;/strong&gt; Regularly back up your encryption keys and store them in a secure location. Losing your keys means losing access to your encrypted data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Firewalls and VPNs
&lt;/h3&gt;

&lt;p&gt;Use a firewall like OPNsense to restrict access to your TrueNAS server. Set up rules to allow only trusted IPs or VPN connections. For remote access, configure a VPN (e.g., WireGuard or OpenVPN) to securely tunnel into your network.&lt;/p&gt;

&lt;p&gt;When configuring your firewall, consider using geo-blocking to restrict access from countries you don’t expect traffic from. Additionally, enable logging to monitor access attempts and identify potential threats. For VPNs, WireGuard is a lightweight and modern option that offers excellent performance and security.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Security Note:&lt;/strong&gt; Avoid using outdated VPN protocols like PPTP, as they are no longer considered secure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Regular Updates and Patching
&lt;/h3&gt;

&lt;p&gt;Keeping your system updated is critical. TrueNAS provides a built-in updater for applying patches and updates. Schedule regular maintenance windows to ensure your system stays secure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Security Note:&lt;/strong&gt; Always test updates in a staging environment before applying them to production systems.&lt;/p&gt;

&lt;p&gt;Updates often include security patches that address newly discovered vulnerabilities. Delaying updates can leave your system exposed to attacks. If possible, enable email notifications for update availability so you’re always informed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintenance and Best Practices
&lt;/h2&gt;

&lt;p&gt;Maintaining your TrueNAS setup is just as important as the initial configuration. Here are some best practices:&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring System Health
&lt;/h3&gt;

&lt;p&gt;Enable email alerts to stay informed about system events. Use tools like Grafana and Prometheus to monitor metrics like disk usage, CPU load, and network traffic.&lt;/p&gt;

&lt;p&gt;Regularly check the SMART status of your drives to identify potential failures before they occur. TrueNAS includes built-in tools for monitoring drive health, but you can also use third-party solutions for more detailed insights.&lt;/p&gt;

&lt;p&gt;💡 Pro Tip: Set up a dashboard in Grafana to visualize key metrics at a glance. This makes it easier to identify trends and spot issues early.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automating Backups
&lt;/h3&gt;

&lt;p&gt;Set up automated snapshots and replication tasks to back up your data. Store backups offsite or in a separate location within your homelab.&lt;/p&gt;

&lt;p&gt;For critical data, consider using a 3-2-1 backup strategy: three copies of your data, stored on two different media types, with one copy offsite. This ensures you’re protected against hardware failures, accidental deletions, and disasters like fires or floods.&lt;/p&gt;

&lt;p&gt;💡 Pro Tip: Use cloud storage services like Backblaze B2 or Wasabi for offsite backups. TrueNAS supports integration with these services for seamless replication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Periodic Security Audits
&lt;/h3&gt;

&lt;p&gt;Review logs and access records regularly. Look for unusual activity and address potential vulnerabilities promptly.&lt;/p&gt;

&lt;p&gt;Security audits should include checking for unused accounts, outdated permissions, and unpatched vulnerabilities. Use tools like Nessus or OpenVAS to scan your network for potential issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling Up: Future-Proofing Your Homelab
&lt;/h2&gt;

&lt;p&gt;As your storage needs grow, you’ll need to scale your TrueNAS setup. Here’s how to prepare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add more drives to your pool or create additional pools for specific workloads.&lt;/li&gt;
&lt;li&gt;Integrate TrueNAS with other homelab services like Proxmox or Kubernetes.&lt;/li&gt;
&lt;li&gt;Stay informed about emerging security trends and adapt your setup accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scaling up often involves adding more hardware, which can introduce new challenges. For example, adding drives to an existing pool may require rebalancing data, which can be time-consuming. Plan for these scenarios in advance to minimize downtime.&lt;/p&gt;

&lt;p&gt;💡 Pro Tip: Use hot-swappable drive bays for easier hardware upgrades. This allows you to replace or add drives without shutting down your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Section: Integrating TrueNAS with Other Services
&lt;/h2&gt;

&lt;p&gt;TrueNAS can be integrated with a variety of services to enhance its functionality. Here are some popular integrations:&lt;/p&gt;

&lt;h3&gt;
  
  
  Media Servers
&lt;/h3&gt;

&lt;p&gt;TrueNAS works seamlessly with media servers like Plex and Emby. Store your media files on a dedicated dataset and configure your media server to access them. This setup allows you to stream movies, TV shows, and music directly from your NAS.&lt;/p&gt;

&lt;p&gt;💡 Pro Tip: Use SSDs for your media dataset if you frequently access large files. This improves performance and reduces buffering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Virtualization Platforms
&lt;/h3&gt;

&lt;p&gt;If you’re running a virtualization platform like Proxmox or VMware, you can use TrueNAS as a shared storage solution. Configure iSCSI or NFS shares to provide high-performance storage for your virtual machines.&lt;/p&gt;

&lt;p&gt;💡 Pro Tip: Use separate datasets for each VM to simplify management and improve performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Section: Advanced Troubleshooting
&lt;/h2&gt;

&lt;p&gt;Even with the best planning, issues can arise. Here’s how to troubleshoot common problems:&lt;/p&gt;

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

&lt;p&gt;If your TrueNAS server is running slowly, check the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disk health: Use SMART tools to identify failing drives.&lt;/li&gt;
&lt;li&gt;Network configuration: Ensure your NICs are configured correctly and aren’t overloaded.&lt;/li&gt;
&lt;li&gt;Resource usage: Monitor CPU and RAM usage to identify bottlenecks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Pro Tip: Use the built-in reporting tools in TrueNAS to visualize performance metrics over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Problems
&lt;/h3&gt;

&lt;p&gt;If users can’t access their data, check the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Permissions: Ensure the correct permissions are set on datasets and shares.&lt;/li&gt;
&lt;li&gt;Network connectivity: Verify that the server is reachable and the correct IP is being used.&lt;/li&gt;
&lt;li&gt;Authentication: Check user accounts and passwords for errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What’s the difference between TrueNAS CORE and SCALE?
&lt;/h3&gt;

&lt;p&gt;CORE is FreeBSD-based and ideal for traditional NAS use. SCALE is Linux-based and supports containers and VMs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use consumer-grade hardware for TrueNAS?
&lt;/h3&gt;

&lt;p&gt;You can, but enterprise-grade hardware (e.g., ECC RAM, server-grade drives) is recommended for reliability and data integrity.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I secure remote access to TrueNAS?
&lt;/h3&gt;

&lt;p&gt;Use a VPN like WireGuard or OpenVPN. Avoid exposing the TrueNAS web interface directly to the internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s the best way to back up TrueNAS data?
&lt;/h3&gt;

&lt;p&gt;Use ZFS snapshots and replication tasks. Store backups offsite or on a separate server for redundancy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🛠️ Recommended Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tools and books mentioned in (or relevant to) this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/dp/B08C4Z69LN?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;Crucial 64GB DDR4 ECC SODIMM Kit&lt;/a&gt; — ECC RAM for data integrity in your NAS or hypervisor ($150-200)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/dp/B0CMQ6SK7W?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;WD Red Plus 8TB NAS HDD&lt;/a&gt; — CMR drive designed for 24/7 NAS operation with RAID support ($140-180)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/dp/B0DCVJ8XTR?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;Beelink EQR6 Mini PC (Ryzen 7 6800U)&lt;/a&gt; — Compact powerhouse for Proxmox or TrueNAS virtualization ($350-500)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/dp/B000FBK3QK?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;APC UPS 1500VA&lt;/a&gt; — Battery backup to protect your homelab from power outages ($170-200)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TrueNAS offers enterprise-grade features for homelabs, but proper configuration is essential for security.&lt;/li&gt;
&lt;li&gt;Use ECC memory, RAID-Z, and VLANs to ensure data integrity and network segmentation.&lt;/li&gt;
&lt;li&gt;Enable encryption, configure firewalls, and use VPNs for secure access.&lt;/li&gt;
&lt;li&gt;Regular updates, backups, and security audits are non-negotiable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.truenas.com/" rel="noopener noreferrer"&gt;TrueNAS Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.zfsbuild.com/" rel="noopener noreferrer"&gt;ZFS Build Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opnsense.org/" rel="noopener noreferrer"&gt;OPNsense Firewall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wireguard.com/" rel="noopener noreferrer"&gt;WireGuard VPN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;Grafana Monitoring&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://orthogonal.info/enterprise-security-at-home-wazuh-suricata-setup/" rel="noopener noreferrer"&gt;Enterprise Security at Home: Wazuh &amp;amp; Suricata Setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orthogonal.info/secure-truenas-plex-setup-homelab/" rel="noopener noreferrer"&gt;Secure TrueNAS Plex Setup for Your Homelab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orthogonal.info/stop-ngrok-tunnels-enterprise-security-homelab/" rel="noopener noreferrer"&gt;Stop Ngrok Tunnels: Enterprise Security at Home&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://orthogonal.info/truenas-setup-guide-enterprise-security-at-home/" rel="noopener noreferrer"&gt;Read the full article on orthogonal.info&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>homelab</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Track Congressional Stock Trades with Python and Free SEC Data</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 13 Apr 2026 18:12:52 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/track-congressional-stock-trades-with-python-and-free-sec-data-309d</link>
      <guid>https://dev.to/orthogonalinfo/track-congressional-stock-trades-with-python-and-free-sec-data-309d</guid>
      <description>&lt;p&gt;Last month I noticed something odd: a senator sold $2M in hotel stocks three days before a travel industry report tanked the sector. Coincidence? Maybe. But it got me wondering — is there an easy way to track what members of Congress are buying and selling?&lt;/p&gt;

&lt;p&gt;Turns out, the STOCK Act of 2012 requires all members of Congress to disclose securities transactions within 45 days. These filings are public. And you can pull them programmatically. I built a Python script that checks for new congressional trades daily, flags the interesting ones, and sends me alerts. Here’s exactly how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Congressional Trades Matter
&lt;/h2&gt;

&lt;p&gt;Members of Congress sit on committees that regulate industries, receive classified briefings, and vote on bills that move markets. Whether they’re trading on insider knowledge is a debate I’ll leave to lawyers. What I care about is this: as a group, congressional traders have historically outperformed the S&amp;amp;P 500 by 6-12% annually, depending on the study you reference. A 2022 paper from the University of Georgia put the figure at 8.9% annualized excess returns for Senate trades.&lt;/p&gt;

&lt;p&gt;Even if you think it’s all luck, following these trades is a free signal you can add to your research process. At worst, it shows you where politically-connected money is flowing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Data Lives
&lt;/h2&gt;

&lt;p&gt;Congressional financial disclosures are filed through two systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Senate&lt;/strong&gt;: efdsearch.senate.gov — the Electronic Financial Disclosures database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;House&lt;/strong&gt;: disclosures-clerk.house.gov — the Clerk of the House system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are publicly searchable, but neither offers a clean API. The Senate site has a search form that returns HTML results. The House site recently added a JSON search endpoint, which is nicer to work with. Several community projects scrape and normalize this data — the most maintained one is the &lt;a href="https://house-stock-watcher-data.s3-us-west-2.amazonaws.com/data/all_transactions.json" rel="noopener noreferrer"&gt;House Stock Watcher dataset&lt;/a&gt; on S3, which gets updated daily.&lt;/p&gt;

&lt;p&gt;For this project, I combined the House Stock Watcher dataset (free, updated daily, clean JSON) with direct scraping of the Senate EFD search for the freshest possible data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Python Script
&lt;/h2&gt;

&lt;p&gt;Here’s the core of what I run. It pulls House transactions from the public S3 dataset, filters for trades above $15,000 (the minimum reporting threshold is $1,001, but small trades are noise), and flags any trades in the last 7 days:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="n"&gt;HOUSE_DATA_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://house-stock-watcher-data.s3-us-west-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.amazonaws.com/data/all_transactions.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_house_trades&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days_back&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$15,001 - $50,000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HOUSE_DATA_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&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;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;trades&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="n"&gt;cutoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;days_back&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;amount_tiers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$15,001 - $50,000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$50,001 - $100,000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$100,001 - $250,000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$250,001 - $500,000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$500,001 - $1,000,000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$1,000,001 - $5,000,000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$5,000,001 - $25,000,000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$25,000,001 - $50,000,000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;tier_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;amount_tiers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;valid_tiers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount_tiers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tier_idx&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;

    &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trades&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;tx_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transaction_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tx_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;valid_tiers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transaction_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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;Each transaction record includes the representative’s name, ticker, transaction type (purchase/sale), amount range, and disclosure date. The amount ranges are annoying — Congress doesn’t disclose exact figures, just brackets — but even the brackets tell you a lot when someone drops $500K+ on a single stock.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filtering for Signal
&lt;/h2&gt;

&lt;p&gt;Raw congressional trade data is noisy. Most trades are mutual fund purchases or routine portfolio rebalancing. The interesting stuff is when you see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Committee-relevant trades&lt;/strong&gt; — A member of the Armed Services Committee buying defense stocks, or a Finance Committee member trading bank shares&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster buys&lt;/strong&gt; — Multiple members buying the same ticker within a short window&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large single-stock positions&lt;/strong&gt; — Anything above $250K in one company&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timing around legislation&lt;/strong&gt; — Trades made shortly before committee votes or bill introductions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I added a scoring function that flags trades matching these patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;COMMITTEE_SECTORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Armed Services&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LMT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RTX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NOC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Energy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;XOM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CVX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EOG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Finance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JPM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BAC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JNJ&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PFE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ABBV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MRK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Technology&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MSFT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GOOGL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AMZN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;META&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score_trade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member_committees&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ticker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Large position = more interesting
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$250,001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$500,001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$1,000,001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;

    &lt;span class="c1"&gt;# Committee relevance
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;committee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tickers&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;COMMITTEE_SECTORS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;committee&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;member_committees&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tickers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="c1"&gt;# Purchase vs sale (purchases are more actionable)
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;purchase&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The committee mapping is simplified here — in production I maintain a fuller list pulled from congress.gov. But even this basic version catches the most egregious cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Daily Alerts
&lt;/h2&gt;

&lt;p&gt;I run this on a &lt;a href="https://www.amazon.com/Raspberry-Model-2GB-Quad-Core-Bluetooth/dp/B09VKJ1KC5?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;Raspberry Pi 4&lt;/a&gt; (affiliate link) sitting in my closet. A cron job runs the script every morning at 7 AM, checks for new trades filed since the last run, and sends me a notification via ntfy (a free, self-hosted push notification tool).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;congress-trades&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://ntfy.sh/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Congressional Trade Alert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# In main loop:
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;trade&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;fetch_house_trades&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days_back&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$50,001 - $100,000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;representative&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ticker&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;send_alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Raspberry Pi draws about 5 watts, costs nothing to run, and handles this job without breaking a sweat. If you don’t want to run your own hardware, a $5/month VPS from any provider works too. I wrote about &lt;a href="https://orthogonal.info/secure-truenas-plex-homelab/" rel="noopener noreferrer"&gt;setting up a homelab for projects like this&lt;/a&gt; if you want to go the self-hosted route.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’ve Learned Running This for 6 Months
&lt;/h2&gt;

&lt;p&gt;A few patterns jumped out after collecting data since late 2025:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure delays are the real problem.&lt;/strong&gt; The 45-day filing window means by the time you see a trade, the move may already be priced in. The most useful trades are the ones filed quickly — within 10-15 days. Some members consistently file within a week; those are the ones I weight highest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cluster signals beat individual trades.&lt;/strong&gt; One senator buying Nvidia means nothing. Three members from different parties all buying Nvidia in the same two-week window? That’s worth investigating. My script tracks cluster buys — 3+ distinct members trading the same ticker within 14 days — and those have been the most actionable signals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sales matter more than purchases for timing.&lt;/strong&gt; Purchases can be routine investment. But when several members suddenly sell the same sector? That’s been a leading indicator for bad news more often than purchases predict good news.&lt;/p&gt;

&lt;p&gt;I won’t claim this is a trading strategy on its own — it’s one data point I check alongside technicals, fundamentals, and &lt;a href="https://orthogonal.info/insider-trading-detector-python-sec-data/" rel="noopener noreferrer"&gt;corporate insider trades from SEC Form 4 filings&lt;/a&gt;. The congressional data adds a political risk dimension that most retail traders ignore entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives and Paid Tools
&lt;/h2&gt;

&lt;p&gt;If you don’t want to build your own, several paid services track this data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quiver Quantitative&lt;/strong&gt; (free tier + paid) — best visualization, shows committee-trade correlations. The free tier covers delayed data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capitol Trades&lt;/strong&gt; (free) — clean interface, basic filtering. No alerts or scoring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unusual Whales&lt;/strong&gt; ($30-100/mo) — includes congressional data alongside options flow. Worth it if you want both in one platform.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I prefer my DIY version because I can customize the scoring, add my own committee mappings, and cross-reference against other datasets I already collect. But if you just want to glance at the data without writing code, Capitol Trades is solid and free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending It
&lt;/h2&gt;

&lt;p&gt;The basic script above gets you 80% of the value. If you want to go further:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add Senate data&lt;/strong&gt; — the EFD search site requires a bit more scraping work since it returns HTML, but BeautifulSoup handles it. A good &lt;a href="https://www.amazon.com/Web-Scraping-Python-Collecting-Modern/dp/1098145348?tag=orthogonalinf-20" rel="noopener noreferrer"&gt;Python web scraping reference&lt;/a&gt; (affiliate link) will save you hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-reference with Polygon.io&lt;/strong&gt; — I use &lt;a href="https://orthogonal.info/pre-ipo-api-sec-filings-spacs-lockup-data/" rel="noopener noreferrer"&gt;Polygon’s market data API&lt;/a&gt; to check price action after each disclosed trade. This lets you backtest whether following congressional trades would have been profitable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build a dashboard&lt;/strong&gt; — Grafana + SQLite gives you a clean visual history. Run it on the same Pi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track state-level trades&lt;/strong&gt; — Some states have their own disclosure requirements for governors and state legislators. Less data, but less competition from other trackers too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full source code for my version is about 400 lines of Python with zero paid dependencies — just stdlib plus BeautifulSoup for the Senate scraping. I might open-source it if there’s interest; drop a comment below if that’d be useful.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I publish daily market intelligence — including congressional trade alerts — on our free Telegram channel. Join &lt;a href="https://t.me/alphasignal822" rel="noopener noreferrer"&gt;Alpha Signal&lt;/a&gt; for daily signals, trade analysis, and macro context. No fluff, no paywalls on the basics.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CSS Gradient Builder: Fixing Annoyances of Existing Tools</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 13 Apr 2026 16:47:44 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/css-gradient-builder-fixing-annoyances-of-existing-tools-1lh6</link>
      <guid>https://dev.to/orthogonalinfo/css-gradient-builder-fixing-annoyances-of-existing-tools-1lh6</guid>
      <description>&lt;p&gt;Last Tuesday I needed a conic gradient. Not a linear one, not a radial one — specifically a conic gradient for a loading spinner I was building. I opened three different gradient generators. None of them supported conic gradients. The ones that did were buried under ads, tracking scripts, and cookie consent banners that took longer to dismiss than the actual gradient took to build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; GradientForge is a free browser-based CSS gradient builder that fixes the annoyances of existing tools — it supports linear, radial, and conic gradients, provides real-time preview with no ads or tracking, outputs clean CSS with vendor prefixes, and includes preset collections for common design patterns.&lt;/p&gt;

&lt;p&gt;So I spent my afternoon building GradientForge instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Existing Gradient Tools
&lt;/h2&gt;

&lt;p&gt;I tested the three most popular gradient generators before writing a single line of code. Here's what I found:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;cssgradient.io&lt;/strong&gt; is the go-to recommendation on Stack Overflow answers from 2019. It handles linear and radial gradients well enough, but it's slow. The page loads with trackers, analytics, and display ads competing for bandwidth. When I tested on a throttled 3G connection, first meaningful paint took over four seconds. For a tool that should generate a CSS property in under a second, that's unacceptable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grabient&lt;/strong&gt; looks beautiful — I'll give it that. But it's primarily a preset gallery with limited customization. Want to add a third color stop? That's buried in the interface. Want conic gradients? Not available. Want to export as SVG for a design file? Nope.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;uiGradients&lt;/strong&gt; follows the same preset-only pattern. Pick from a curated list, copy the CSS. No custom stop positions, no angle fine-tuning, no easing control. It's a gradient menu, not a gradient builder.&lt;/p&gt;

&lt;p&gt;Every single one of these tools was missing at least one thing I needed: conic gradient support, easing between color stops, SVG export, or just basic speed. I wanted all of those in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GradientForge Actually Does
&lt;/h2&gt;

&lt;p&gt;GradientForge supports all three CSS gradient types: linear, radial, and conic. You pick your type, adjust the parameters, and see the result update in real-time on a full-screen canvas preview. The CSS code appears below, ready to copy with one click or keyboard shortcut (Ctrl+C when nothing is selected).&lt;/p&gt;

&lt;p&gt;The color stop system works the way it should. Click a color picker, drag the position handle along the gradient bar, or type an exact percentage. Double-click the bar to add a new stop at that position — the tool interpolates the correct color automatically. You can have up to 10 stops per gradient, which covers every practical use case I've encountered.&lt;/p&gt;

&lt;p&gt;The feature I'm most proud of is the &lt;strong&gt;easing system&lt;/strong&gt;. Standard CSS gradients transition linearly between color stops, which often produces muddy middle zones where colors mix in ugly ways. GradientForge generates additional intermediate stops that follow an easing curve — ease-in, ease-out, ease-in-out, or stepped transitions. The result is smoother, more visually pleasing gradients without manual fine-tuning of each stop position.&lt;/p&gt;

&lt;p&gt;Here's what happens technically: when you select an easing function, the tool interpolates 8 additional color stops between each pair of your original stops, positioning them along the chosen easing curve. The browser sees a gradient with many stops, but the transitions follow a cubic or stepped curve instead of a linear one. The output CSS is longer, but the visual result is noticeably better, especially for gradients spanning complementary colors.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;GradientForge is a single HTML file with inline CSS and JavaScript — zero dependencies. No React, no Tailwind, no build step, no node_modules. The entire tool is about 36KB — smaller than most hero images. It loads in under 100ms on any modern connection.&lt;/p&gt;

&lt;p&gt;The architecture is straightforward state management. A single JavaScript object holds the current gradient configuration: type, angle, color stops, easing mode, and type-specific parameters. Every time any control changes, the entire UI re-renders from that state object. It sounds wasteful, but with only a few DOM elements to update, each render cycle takes under 2ms.&lt;/p&gt;

&lt;p&gt;The color stop bar uses pointer events for drag handling. Each stop is a positioned &lt;code&gt;div&lt;/code&gt; inside the bar container. On mousedown, I capture the element, switch to mousemove tracking on the document (not the bar — that prevents losing the drag when the cursor moves fast), and compute the percentage position from the cursor's X coordinate relative to the bar's bounding rect. Touch events follow the same pattern for mobile support.&lt;/p&gt;

&lt;p&gt;For color interpolation, I convert hex colors to RGB components, interpolate each channel independently, and convert back. This happens in sRGB space, which isn't perceptually uniform — I'd like to add OKLCH interpolation in a future version for even smoother results. But for most practical gradients, sRGB interpolation is indistinguishable from perceptual to human eyes.&lt;/p&gt;

&lt;p&gt;The SVG export translates CSS gradient parameters into SVG gradient elements. Linear gradients map directly to &lt;code&gt;&amp;lt;linearGradient&amp;gt;&lt;/code&gt; with computed x1/y1/x2/y2 coordinates derived from the CSS angle. Radial gradients use &lt;code&gt;&amp;lt;radialGradient&amp;gt;&lt;/code&gt; with center positions. Conic gradients don't have a native SVG equivalent, so the tool falls back to a linear approximation — not perfect, but useful enough for most design workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The URL State Trick
&lt;/h2&gt;

&lt;p&gt;Every gradient configuration is encoded in the URL query parameters. Change a color, move a stop, switch the type — the URL updates silently via &lt;code&gt;history.replaceState&lt;/code&gt;. This means you can share a gradient by sharing the URL. No accounts, no saving to a database, no server-side state. The recipient opens the link and sees your exact gradient configuration ready to use.&lt;/p&gt;

&lt;p&gt;The encoding is compact: gradient type is a single character (l/r/c), stops are comma-separated hex:position pairs, and type-specific parameters use short keys. A three-stop linear gradient with easing encodes to about 120 characters in the URL — short enough to paste in a Slack message without it looking intimidating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy and Performance
&lt;/h2&gt;

&lt;p&gt;Everything runs in your browser. There's no server processing, no analytics tracking your color choices, no data leaving your machine. The tool works completely offline once loaded — I included a service worker that caches all assets. Install it as a PWA and you've got a native-feeling gradient builder that works on a plane.&lt;/p&gt;

&lt;p&gt;I ran Lighthouse on the deployed version: 100 across all four categories. Performance, accessibility, best practices, SEO — all perfect scores. That's what happens when your entire app is 36KB of self-contained HTML with proper ARIA labels and semantic markup.&lt;/p&gt;

&lt;h2&gt;
  
  
  12 Built-In Presets
&lt;/h2&gt;

&lt;p&gt;Sometimes you don't want to build from scratch. GradientForge includes 12 presets — Sunset, Ocean, Forest, Flame, Night, Peach, Arctic, Berry, Candy, Mint, Dusk, and Neon. Click one to load it, then customize from there. They're starting points, not endpoints.&lt;/p&gt;

&lt;p&gt;The presets also serve as a discovery tool. If you're not sure what conic gradients look like, hit the Random button. It generates a random type, random angle, random colors, and random number of stops. Hit it ten times and you'll have a better intuition for what each gradient type does than reading any tutorial could give you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dark Mode and Mobile
&lt;/h2&gt;

&lt;p&gt;The interface respects your system color scheme preference automatically. No toggle needed — though I might add one in a future update for users who want to test their gradient against both backgrounds. On mobile, the layout shifts from a side-by-side view (preview + controls) to a stacked view with the preview on top and controls scrollable below. Touch targets are 44px minimum for comfortable thumb navigation.&lt;/p&gt;

&lt;p&gt;I tested at 320px width (iPhone SE), 768px (iPad), and 1440px (desktop). The gradient preview always takes up as much space as possible — that's the point of the tool, after all. Controls compress but remain usable at every breakpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I have a short list of features I want to add: OKLCH color interpolation for perceptually smoother gradients, a gradient animation builder (because CSS can animate gradient positions), multi-gradient layering (stack multiple gradients on one element), and an accessibility checker that warns when your gradient doesn't meet contrast requirements for text overlays.&lt;/p&gt;

&lt;p&gt;For now, GradientForge does exactly what I needed: build any CSS gradient, with any number of stops, with smooth easing, in any of the three gradient types, and copy the result in one click. No ads, no tracking, no signup. Just gradients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://gradientforge.orthogonal.info/" rel="noopener noreferrer"&gt;Try GradientForge →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Does GradientForge support conic gradients?
&lt;/h3&gt;

&lt;p&gt;Yes — this was the primary motivation for building it. Most existing CSS gradient generators only support linear and radial gradients. GradientForge supports all three types: linear, radial, and conic, with full control over angle, color stops, and positioning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is GradientForge free to use?
&lt;/h3&gt;

&lt;p&gt;Completely free, with no ads, tracking scripts, or account requirements. It runs entirely in your browser — no data is sent to any server.&lt;/p&gt;

&lt;h3&gt;
  
  
  What browsers support conic gradients?
&lt;/h3&gt;

&lt;p&gt;All modern browsers support conic gradients: Chrome 69+, Firefox 83+, Safari 12.1+, and Edge 79+. GradientForge includes vendor prefix options for broader compatibility and shows a browser support indicator for each gradient type you create.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MDN Web Docs. "&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient" rel="noopener noreferrer"&gt;CSS conic-gradient()&lt;/a&gt;." &lt;em&gt;Mozilla&lt;/em&gt;, 2026.&lt;/li&gt;
&lt;li&gt;Can I Use. "&lt;a href="https://caniuse.com/css-conic-gradients" rel="noopener noreferrer"&gt;CSS Conic Gradients browser support&lt;/a&gt;." &lt;em&gt;Can I Use&lt;/em&gt;, 2026.&lt;/li&gt;
&lt;li&gt;W3C. "&lt;a href="https://www.w3.org/TR/css-images-4/" rel="noopener noreferrer"&gt;CSS Images Module Level 4&lt;/a&gt;." &lt;em&gt;W3C&lt;/em&gt;, 2024.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>devtools</category>
      <category>javascript</category>
    </item>
    <item>
      <title>OpenClaw Setup: Zero to Autonomous AI Mastery</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Sun, 12 Apr 2026 16:49:30 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/openclaw-setup-zero-to-autonomous-ai-mastery-4374</link>
      <guid>https://dev.to/orthogonalinfo/openclaw-setup-zero-to-autonomous-ai-mastery-4374</guid>
      <description>&lt;p&gt;Setting up OpenClaw is easy. Setting it up &lt;em&gt;right&lt;/em&gt; so your AI agent actually does useful work autonomously takes some know-how.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes OpenClaw Different
&lt;/h2&gt;

&lt;p&gt;Unlike ChatGPT or Claude, which respond to individual prompts, OpenClaw creates &lt;strong&gt;persistent AI agents&lt;/strong&gt; that remember between sessions, act autonomously through cron jobs, use real tools like browser automation and APIs, and self-improve by editing their own configuration.&lt;/p&gt;

&lt;p&gt;With Hostinger now offering 1-click OpenClaw deployment, the barrier to entry has never been lower. But the gap between &lt;em&gt;installed&lt;/em&gt; and &lt;em&gt;productive&lt;/em&gt; is where most people get stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3 Mistakes New OpenClaw Users Make
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Generic SOUL.md
&lt;/h3&gt;

&lt;p&gt;Your SOUL.md file is your agent's personality and decision-making framework. A generic "you are a helpful assistant" produces generic results. A well-crafted SOUL.md with specific principles, boundaries, and communication style creates an agent that feels like a capable teammate.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No Memory Protocol
&lt;/h3&gt;

&lt;p&gt;Without structured memory, every session starts from scratch. The 3-layer memory system (State, Journal, Knowledge) gives your agent continuity. It remembers what worked, what failed, and what it learned — across sessions and days.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Manual-Only Operation
&lt;/h3&gt;

&lt;p&gt;The real power of OpenClaw is autonomous operation via cron jobs. An agent that only responds to messages is using 10% of its potential. Cron jobs let your agent monitor, create, publish, and optimize while you sleep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start Checklist
&lt;/h2&gt;

&lt;p&gt;If you're setting up OpenClaw for the first time, here's what actually matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Edit SOUL.md&lt;/strong&gt; — give your agent a specific personality and principles, not generic instructions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit USER.md&lt;/strong&gt; — tell it who you are and what you need done&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit TOOLS.md&lt;/strong&gt; — add your local services (Home Assistant, NAS, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create the data/ directory&lt;/strong&gt; with &lt;code&gt;state.json&lt;/code&gt;, &lt;code&gt;knowledge.md&lt;/code&gt;, and &lt;code&gt;journal/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up 3 starter crons&lt;/strong&gt;: email check (every 2h), weather (morning), RSS monitor (every 4h)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure browser-agent&lt;/strong&gt; for web automation tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test a heartbeat cycle&lt;/strong&gt; to verify autonomous operation works end-to-end&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create HEARTBEAT.md&lt;/strong&gt; with your periodic task checklist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference between a toy setup and a useful one is usually about 30 minutes of configuration. The memory protocol alone transforms the experience from "stateless chatbot" to "AI that actually knows what happened yesterday."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned Running This in Production
&lt;/h2&gt;

&lt;p&gt;I've been running an OpenClaw agent with 31 skills and 25+ daily cron jobs since March 2026. A few things surprised me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory matters more than model choice.&lt;/strong&gt; Switching from GPT-4o to Claude Opus made less difference than implementing proper state persistence. An agent that remembers its failures and learns from them outperforms a smarter agent that starts fresh every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cron patterns need iteration.&lt;/strong&gt; My first cron jobs ran too frequently and produced noise. The sweet spot was fewer jobs that batch related checks together. One heartbeat that checks email + calendar + weather beats three separate crons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills compound.&lt;/strong&gt; Each new skill makes existing ones more useful. Browser automation + RSS monitoring = automatic content research. Trading data + newsletter publishing = daily market intelligence on autopilot.&lt;/p&gt;

&lt;p&gt;The full setup guide with templates, 30 production-tested cron patterns, and SOUL.md examples is available at &lt;a href="https://orthogonal.info/openclaw-setup-guide-mastery-pack/" rel="noopener noreferrer"&gt;orthogonal.info&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Questions about OpenClaw setup? Drop them in the comments — I've probably hit the same wall.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>automation</category>
      <category>webdev</category>
    </item>
    <item>
      <title>GitOps vs GitHub Actions: Security-First in Production</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Sat, 11 Apr 2026 18:17:34 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/gitops-vs-github-actions-security-first-in-production-18in</link>
      <guid>https://dev.to/orthogonalinfo/gitops-vs-github-actions-security-first-in-production-18in</guid>
      <description>&lt;p&gt;Last month I migrated two production clusters from GitHub Actions-only deployments to a hybrid GitOps setup with ArgoCD. The trigger? A misconfigured workflow secret that exposed an AWS key for 11 minutes before our scanner caught it. Nothing happened — this time. But it made me rethink how we handle the boundary between CI and CD.&lt;/p&gt;

&lt;p&gt;Here’s what I learned about running both tools securely in production, and when each one actually makes sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitOps: Let Git Be the Only Way In
&lt;/h2&gt;

&lt;p&gt;GitOps treats Git as the single source of truth for your cluster state. You define what should exist in a repo, and an agent like ArgoCD or Flux continuously reconciles reality to match. No one SSHs into production. No one runs &lt;code&gt;kubectl apply&lt;/code&gt; by hand.&lt;/p&gt;

&lt;p&gt;The security model here is simple: the cluster pulls config from Git. The agent runs inside the cluster with the minimum permissions needed to apply manifests. Your developers never need direct cluster access — they open a PR, it gets reviewed, merged, and the agent picks it up.&lt;/p&gt;

&lt;p&gt;This is a massive reduction in attack surface. In a traditional CI/CD model, your pipeline needs credentials to push to the cluster. With GitOps, those credentials stay inside the cluster.&lt;/p&gt;

&lt;p&gt;Here’s a basic ArgoCD Application manifest:&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;my-app&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;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/my-org/my-app-config&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;HEAD&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;.&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;my-app-namespace&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;selfHeal: true&lt;/code&gt; setting is important — if someone does manage to modify a resource directly in the cluster, ArgoCD will revert it to match Git. That’s drift detection for free.&lt;/p&gt;

&lt;p&gt;One gotcha: make sure you enforce branch protection on your GitOps repos. I’ve seen teams set up ArgoCD perfectly, then leave the &lt;code&gt;main&lt;/code&gt; branch unprotected. Anyone with repo write access can then deploy anything. Always require reviews and status checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions: Powerful but Exposed
&lt;/h2&gt;

&lt;p&gt;GitHub Actions is a different animal. It’s event-driven — push code, open a PR, hit a schedule, and workflows fire. That flexibility is exactly what makes it harder to secure.&lt;/p&gt;

&lt;p&gt;Every GitHub Actions workflow that deploys to production needs some form of credential. Even with OIDC federation (which you should absolutely be using), there are still risks. Third-party actions can be compromised. Workflow files can be modified in feature branches. Secrets can leak through step outputs if you’re not careful.&lt;/p&gt;

&lt;p&gt;Here’s a typical deployment workflow:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Kubernetes&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&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;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;Configure kubectl&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/setup-kubectl@v3&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;Deploy application&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubectl apply -f k8s/deployment.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;environment: production&lt;/code&gt; — that enables environment protection rules, so deployments require manual approval. Without it, any push to &lt;code&gt;main&lt;/code&gt; goes straight to prod. I always set this up, even on small projects.&lt;/p&gt;

&lt;p&gt;The bigger issue is that GitHub Actions workflows are imperative. You’re writing step-by-step instructions that execute on a runner with network access. Compare that to GitOps where you declare “this is what should exist” and an agent figures out the rest. The imperative model has more moving parts, and more places for things to go wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Each One Wins on Security
&lt;/h2&gt;

&lt;p&gt;After running both in production, here’s how I’d break it down:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access control&lt;/strong&gt; — GitOps wins. The agent pulls from Git, so your CI system never needs cluster credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secret handling&lt;/strong&gt; — GitOps is cleaner. You pair it with External Secrets Operator or Sealed Secrets and your Git repo never contains actual credentials. GitHub Actions has encrypted secrets, but they’re injected into the runner environment at build time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit trail&lt;/strong&gt; — GitOps. Every change is a Git commit with an author, timestamp, and review trail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt; — GitHub Actions. Running test suites, building container images, scanning for vulnerabilities — these are CI tasks, and GitHub Actions handles them well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed of setup&lt;/strong&gt; — GitHub Actions. You can go from zero to deployed in an afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hybrid Approach (What Actually Works)
&lt;/h2&gt;

&lt;p&gt;Most teams end up running both, and it’s the right call. Use GitHub Actions for CI — build, test, scan, push images. Use GitOps for CD — let ArgoCD or Flux handle what’s running in the cluster.&lt;/p&gt;

&lt;p&gt;The boundary is important: GitHub Actions should never directly &lt;code&gt;kubectl apply&lt;/code&gt; to production. Instead, it updates the image tag in your GitOps repo (via a PR or direct commit to a deploy branch), and the GitOps agent picks it up.&lt;/p&gt;

&lt;p&gt;This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full Git audit trail for all production changes&lt;/li&gt;
&lt;li&gt;No cluster credentials in your CI system&lt;/li&gt;
&lt;li&gt;Automatic drift detection and self-healing&lt;/li&gt;
&lt;li&gt;The flexibility of GitHub Actions for everything that isn’t deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding Security Scanning to the Pipeline
&lt;/h2&gt;

&lt;p&gt;Whether you use GitOps, GitHub Actions, or both, you need automated security checks. I run Trivy on every image build and OPA/Gatekeeper for policy enforcement in the cluster.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;exit-code: 1&lt;/code&gt; setting in Trivy means the workflow fails if critical or high vulnerabilities are found. No exceptions. It’s caught real issues — including supply chain problems in base images that would have made it to prod otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d Do Starting Fresh
&lt;/h2&gt;

&lt;p&gt;If I were setting up a new production Kubernetes environment today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ArgoCD for all cluster deployments, with strict branch protection and required reviews&lt;/li&gt;
&lt;li&gt;GitHub Actions for CI only — build, test, scan, push to registry&lt;/li&gt;
&lt;li&gt;External Secrets Operator for credentials, never stored in Git&lt;/li&gt;
&lt;li&gt;OPA Gatekeeper for policy enforcement&lt;/li&gt;
&lt;li&gt;Trivy in CI, plus periodic scanning of running images&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The investment in GitOps pays off fast once you’re past the initial setup. The first time you need to answer “what changed?” during a 2 AM incident and the answer is right there in the Git log, you’ll be glad you did it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://orthogonal.info/gitops-vs-github-actions-security-first-production/" rel="noopener noreferrer"&gt;orthogonal.info&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>security</category>
      <category>github</category>
    </item>
    <item>
      <title>Stop Ngrok Tunnels: Enterprise Security Practices for Your Homelab</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Sat, 11 Apr 2026 16:46:53 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/stop-ngrok-tunnels-enterprise-security-practices-for-your-homelab-5eh0</link>
      <guid>https://dev.to/orthogonalinfo/stop-ngrok-tunnels-enterprise-security-practices-for-your-homelab-5eh0</guid>
      <description>&lt;p&gt;Ngrok is one of those tools that's dangerously easy to love. Spin up a tunnel, get a public URL, share your local service with the world. But that convenience hides real risk — especially if you're running a homelab where tunnels tend to linger longer than they should.&lt;/p&gt;

&lt;p&gt;I've been running a homelab for years, and I'll admit: I've left Ngrok tunnels running overnight more times than I'd like. Each one was an open door I forgot to close. Here's what I've learned about managing them properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Ngrok Tunnels Are a Security Problem
&lt;/h2&gt;

&lt;p&gt;Every active Ngrok tunnel increases your attack surface. Attackers actively scan public Ngrok URLs for exposed services — databases without auth, admin panels, development APIs. If you're not monitoring your tunnels, you're essentially leaving a backdoor open.&lt;/p&gt;

&lt;p&gt;The bigger issue: Ngrok tunnels bypass your firewall rules. That carefully configured pfSense or OPNsense setup? Ngrok sidesteps it entirely by tunneling outbound through HTTPS. Your IDS/IPS won't see the inbound traffic because it arrives through Ngrok's infrastructure.&lt;/p&gt;

&lt;p&gt;Temporary tunnels created for "quick testing" are the worst offenders. They stay active long after you've moved on to something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enterprise Practices That Work at Home
&lt;/h2&gt;

&lt;p&gt;You don't need Splunk or a SOC team to manage tunnels properly. Here's what actually works:&lt;/p&gt;

&lt;h3&gt;
  
  
  Least Privilege
&lt;/h3&gt;

&lt;p&gt;Only expose what's absolutely necessary. If you're testing a webhook, limit access to the IP ranges of the service provider. Use Ngrok's built-in access control rather than leaving tunnels wide open.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;p&gt;Ngrok exposes a local API at &lt;code&gt;http://localhost:4040/api/tunnels&lt;/code&gt;. Use it. A simple script can check for active tunnels and alert you:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:4040/api/tunnels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tunnels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tunnels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tunnel&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tunnels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tunnel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stopping tunnel: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tunnel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tunnel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automated Cleanup
&lt;/h3&gt;

&lt;p&gt;Schedule tunnel termination so you don't rely on remembering. A systemd timer or cron job works fine:&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="c"&gt;# Crontab: check every hour, kill active tunnels&lt;/span&gt;
0 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; ngrok api tunnels list | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ngrok api tunnels stop &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I prefer systemd timers over cron for this — they're easier to debug and give you better logging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling Down Enterprise Tools
&lt;/h2&gt;

&lt;p&gt;You don't need Datadog. Here's what works for a homelab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus + Grafana&lt;/strong&gt; for monitoring tunnel activity and setting alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OAuth2 Proxy&lt;/strong&gt; in front of exposed services for authentication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VLANs&lt;/strong&gt; to isolate services exposed via Ngrok from the rest of your network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traefik or Nginx&lt;/strong&gt; as a reverse proxy for SSL termination, auth, and rate limiting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is layers. No single tool solves everything, but stacking a few open-source options gives you real protection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Configurations Worth Setting Up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Custom domains&lt;/strong&gt; make your tunnels less predictable and let you apply stricter DNS policies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limiting&lt;/strong&gt; prevents abuse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rate_limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"requests_per_second"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Webhook validation&lt;/strong&gt; — if you're using Ngrok for webhook testing, always verify HMAC signatures or use token-based auth. Don't trust incoming requests just because they hit your tunnel URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every active tunnel is an open door. Close what you're not using.&lt;/li&gt;
&lt;li&gt;Ngrok bypasses your firewall — treat tunnels as external access points, not internal tools.&lt;/li&gt;
&lt;li&gt;Automate cleanup with cron or systemd. Don't rely on memory.&lt;/li&gt;
&lt;li&gt;Use open-source monitoring (Prometheus/Grafana) instead of expensive enterprise tools.&lt;/li&gt;
&lt;li&gt;Layer your defenses: VLANs, reverse proxies, authentication, rate limiting.&lt;/li&gt;
&lt;li&gt;Consider a zero-trust approach even in your homelab — verify every connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Got a Ngrok horror story? I've definitely had my share. Drop a comment — I'm curious what others have run into.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>homelab</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Secure TrueNAS Plex Setup for Your Homelab</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Fri, 10 Apr 2026 18:13:44 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/secure-truenas-plex-setup-for-your-homelab-42i3</link>
      <guid>https://dev.to/orthogonalinfo/secure-truenas-plex-setup-for-your-homelab-42i3</guid>
      <description>&lt;p&gt;Learn how to set up Plex on TrueNAS with enterprise-grade security practices tailored for home use. Protect your data while enjoying smooth media streaming.&lt;/p&gt;

&lt;h2&gt;
  
  
  TrueNAS and Plex
&lt;/h2&gt;

&lt;p&gt;The error message was cryptic: “Permission denied.” You just wanted to stream your favorite movie, but Plex refused to cooperate. Meanwhile, your TrueNAS server was humming along, oblivious to the chaos. If you’ve ever struggled with setting up a secure and functional Plex server on TrueNAS, you’re not alone.&lt;/p&gt;

&lt;p&gt;TrueNAS is a powerful, open-source storage solution designed for reliability and scalability. It’s built on ZFS, a solid file system that offers advanced features like snapshots, compression, and data integrity checks. For homelab enthusiasts, TrueNAS is often the backbone of their setup, providing centralized storage for everything from personal files to virtual machines.&lt;/p&gt;

&lt;p&gt;Plex, on the other hand, is the go-to media server for streaming movies, TV shows, and music across devices. Combining TrueNAS and Plex allows you to use enterprise-grade storage for your media library while enjoying smooth streaming. But here’s the catch: without proper security measures, you’re leaving your data—and potentially your network—vulnerable to attacks.&lt;/p&gt;

&lt;p&gt;TrueNAS and Plex are popular choices for homelab setups because they complement each other perfectly. TrueNAS ensures your data is stored securely and efficiently, while Plex provides a user-friendly interface for accessing your media. However, combining these two requires careful planning to avoid common pitfalls such as permission issues, performance bottlenecks, and security vulnerabilities.&lt;/p&gt;

&lt;p&gt;For example, many users encounter issues with Plex not being able to access their media files due to incorrect permissions on the TrueNAS side. This is often caused by a misunderstanding of how TrueNAS handles datasets and user permissions. Also, without proper network isolation, your Plex server could inadvertently expose your TrueNAS system to external threats.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Pro Tip:&lt;/strong&gt; Before starting, map out your homelab architecture on paper or with a tool like draw.io. This will help you visualize how Plex and TrueNAS will interact within your network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Preparing Your Homelab for Secure Deployment
&lt;/h2&gt;

&lt;p&gt;Before diving into the installation, let’s talk about the foundation: your homelab’s hardware and network setup. A secure deployment starts with the right infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardware Requirements:&lt;/strong&gt; TrueNAS requires a machine with ECC (Error-Correcting Code) RAM for data integrity, multiple hard drives for ZFS pools, and a CPU with virtualization support if you plan to run additional services. Plex, while less demanding, benefits from a CPU with good transcoding capabilities, especially if you stream to multiple devices simultaneously.&lt;/p&gt;

&lt;p&gt;For example, if you plan to stream 4K content to multiple devices, a CPU with hardware transcoding support (such as Intel Quick Sync or an NVIDIA GPU) can significantly improve performance. On the storage side, using SSDs for your ZFS cache can speed up access to frequently used files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network Isolation:&lt;/strong&gt; Your homelab should be isolated from your main network. This is where VLANs (Virtual LANs) come into play. By segmenting your network, you can ensure that devices in your homelab don’t have unrestricted access to the rest of your network.&lt;/p&gt;

&lt;p&gt;For instance, you can configure your router or managed switch to create a VLAN specifically for your homelab. This VLAN can include your TrueNAS server, Plex server, and any other devices you use for testing or development. By applying firewall rules, you can control which devices can communicate with each other and with the internet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Security Note:&lt;/strong&gt; Always configure your firewall to block unnecessary inbound and outbound traffic. Open ports are an open invitation for attackers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also, consider using a dedicated firewall like OPNsense to manage traffic between VLANs. This gives you granular control over what devices can communicate with your homelab. For example, you can allow Plex to access the internet for updates but block it from communicating with other devices outside its VLAN.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Example: Creating a VLAN in OPNsense
vlan create 10
vlan set description "Homelab VLAN"
vlan assign interface em0

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing and Configuring TrueNAS
&lt;/h2&gt;

&lt;p&gt;With your hardware and network ready, it’s time to install TrueNAS. The process is straightforward, but there are a few critical steps to ensure a secure setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Installation&lt;/strong&gt;&lt;br&gt;
Download the TrueNAS ISO from the official website and create a bootable USB drive using tools like Rufus or BalenaEtcher. Boot your server from the USB and follow the installation wizard. Choose a strong root password during setup—this is your first line of defense.&lt;/p&gt;

&lt;p&gt;During installation, you’ll be prompted to configure your network settings. Make sure to assign a static IP address to your TrueNAS server. This makes it easier to access the web interface and ensures that your server remains accessible even if your router reboots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: ZFS Pools&lt;/strong&gt;&lt;br&gt;
Once TrueNAS is installed, log into the web interface and navigate to the Storage section. Create ZFS pools using your hard drives. For Plex, it’s best to create a dedicated dataset for your media library. This allows you to set specific permissions and quotas.&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="c"&gt;# Example: Creating a dataset for Plex media&lt;/span&gt;
zfs create tank/plex_media
zfs &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;quota&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;500G tank/plex_media
zfs &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on tank/plex_media
zfs &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;acltype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;posixacl tank/plex_media

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: User Permissions&lt;/strong&gt;&lt;br&gt;
Create a dedicated user for Plex with restricted access. Assign this user permissions to the Plex dataset only. This prevents Plex from accessing other parts of your storage.&lt;/p&gt;

&lt;p&gt;To do this, navigate to the Users section in the TrueNAS web interface and create a new user. Assign this user to a group specifically created for Plex. Then, use ACLs (Access Control Lists) to grant the group read/write access to the Plex dataset.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Pro Tip:&lt;/strong&gt; Use ACLs (Access Control Lists) for fine-grained permission control. TrueNAS makes this easy via its web interface.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you encounter issues with permissions, check the dataset’s ACL settings and ensure that the Plex user has the necessary access. A common mistake is forgetting to apply changes after modifying ACLs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting Up Plex with Enterprise Security Practices
&lt;/h2&gt;

&lt;p&gt;With TrueNAS configured, it’s time to install Plex. While Plex is relatively simple to set up, securing it requires extra effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Installation&lt;/strong&gt;&lt;br&gt;
TrueNAS SCALE users can install Plex via the built-in Apps section. For TrueNAS CORE, you’ll need to create a jail and install Plex manually.&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="c"&gt;# Example: Installing Plex in a TrueNAS jail&lt;/span&gt;
pkg &lt;span class="nb"&gt;install &lt;/span&gt;plexmediaserver
sysrc &lt;span class="nv"&gt;plexmediaserver_enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YES
service plexmediaserver start

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Securing Plex&lt;/strong&gt;&lt;br&gt;
Enable SSL for Plex to encrypt traffic between your server and clients. You can use a self-signed certificate or integrate with Let’s Encrypt for a trusted certificate. Also, set a strong password for your Plex account and enable two-factor authentication.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Security Note:&lt;/strong&gt; Disable remote access unless absolutely necessary. If you must enable it, use a VPN to secure the connection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Minimizing Attack Vectors&lt;/strong&gt;&lt;br&gt;
Restrict Plex’s network access using firewall rules. For example, block Plex from accessing the internet except for updates. This reduces the risk of data leaks.&lt;/p&gt;

&lt;p&gt;Another way to secure Plex is by using a reverse proxy like Nginx or Traefik. This allows you to manage SSL certificates and enforce additional security measures such as rate limiting and IP whitelisting.&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="c1"&gt;# Example: Configuring Nginx as a reverse proxy for Plex&lt;/span&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;443&lt;/span&gt; &lt;span class="s"&gt;ssl&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;plex.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/ssl/plex.crt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/ssl/plex.key&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://localhost:32400&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ongoing Maintenance and Security Monitoring
&lt;/h2&gt;

&lt;p&gt;Setting up Plex and TrueNAS is only half the battle. Maintaining their security requires regular updates and monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regular Updates:&lt;/strong&gt; Both TrueNAS and Plex release updates to patch vulnerabilities. Schedule regular update checks and apply patches promptly. For TrueNAS SCALE, updates can be applied directly from the web interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log Monitoring:&lt;/strong&gt; TrueNAS and Plex generate logs that can help you identify suspicious activity. Set up log forwarding to a centralized logging solution like Graylog or ELK for easier analysis.&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="c"&gt;# Example: Forwarding logs from TrueNAS&lt;/span&gt;
syslogd &lt;span class="nt"&gt;-a&lt;/span&gt; graylog.local:514

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Disaster Recovery:&lt;/strong&gt; Automate backups of your Plex dataset and TrueNAS configuration. Store backups on a separate device or cloud storage to ensure recovery in case of hardware failure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Pro Tip:&lt;/strong&gt; Test your backups periodically. A backup is useless if it doesn’t work when you need it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also, consider implementing a snapshot schedule for your ZFS pools. Snapshots allow you to roll back to a previous state in case of accidental data deletion or corruption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Networking for Plex and TrueNAS
&lt;/h2&gt;

&lt;p&gt;For users looking to take their homelab to the next level, advanced networking configurations can improve both security and performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using VLANs:&lt;/strong&gt; As mentioned earlier, VLANs are essential for network isolation. However, you can also use VLANs to prioritize traffic. For example, you can assign a higher priority to Plex traffic to ensure smooth streaming even during heavy network usage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implementing QoS:&lt;/strong&gt; Quality of Service (QoS) settings on your router or switch can help manage bandwidth allocation. This is particularly useful if you have multiple users accessing Plex simultaneously.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Example: Configuring QoS for Plex traffic
qos set priority high plex_vlan

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Pro Tip:&lt;/strong&gt; Use tools like Wireshark to analyze network traffic and identify bottlenecks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🛠️ Recommended Resources:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tools and books mentioned in (or relevant to) this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crucial 64GB DDR4 ECC SODIMM Kit — ECC RAM for data integrity in your NAS or hypervisor ($150-200)&lt;/li&gt;
&lt;li&gt;WD Red Plus 8TB NAS HDD — CMR drive designed for 24/7 NAS operation with RAID support ($140-180)&lt;/li&gt;
&lt;li&gt;Protectli Vault FW4B — Fanless mini PC perfect for pfSense/OPNsense firewall ($300-400)&lt;/li&gt;
&lt;li&gt;UniFi Dream Machine Pro — All-in-one network appliance with IDS/IPS and VLAN support ($379-399)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  main points
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TrueNAS and Plex make a powerful combination for homelabs, but security must be a priority.&lt;/li&gt;
&lt;li&gt;Isolate your homelab using VLANs and firewall rules to protect your main network.&lt;/li&gt;
&lt;li&gt;Use ZFS datasets and ACLs to control access to your media library.&lt;/li&gt;
&lt;li&gt;Secure Plex with SSL, strong passwords, and restricted network access.&lt;/li&gt;
&lt;li&gt;Regular updates, log monitoring, and automated backups are essential for ongoing security.&lt;/li&gt;
&lt;li&gt;Advanced networking configurations like VLANs and QoS can improve performance and security.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have questions or tips about securing your homelab? Drop a comment or reach out on Twitter. Next week, we’ll explore setting up OPNsense with VLANs for advanced network segmentation. Stay tuned!&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://orthogonal.info/truenas-setup-guide-enterprise-security-homelab/" rel="noopener noreferrer"&gt;TrueNAS Setup Guide: Enterprise Security for Your Homelab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orthogonal.info/home-network-segmentation-with-opnsense/" rel="noopener noreferrer"&gt;Home Network Segmentation with OPNsense&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orthogonal.info/best-drives-for-truenas-in-2026-hdds-ssds-and-what-i-actually-run/" rel="noopener noreferrer"&gt;Best Drives for TrueNAS 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Get daily AI-powered market intelligence.&lt;/strong&gt; Join &lt;a href="https://t.me/alphasignal822" rel="noopener noreferrer"&gt;Alpha Signal&lt;/a&gt; — free market briefs, security alerts, and dev tool recommendations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📋 Disclosure:&lt;/strong&gt; Some links in this article are affiliate links. If you purchase through these links, I earn a small commission at no extra cost to you. I only recommend products I’ve personally used or thoroughly evaluated. This helps support orthogonal.info and keeps the content free.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://orthogonal.info/secure-truenas-plex-setup-homelab/" rel="noopener noreferrer"&gt;orthogonal.info&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>homelab</category>
      <category>security</category>
      <category>linux</category>
      <category>devops</category>
    </item>
    <item>
      <title>Build a Free VPN with Cloudflare Tunnel &amp; WARP (2026 Guide)</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Fri, 10 Apr 2026 16:47:24 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/build-a-free-vpn-with-cloudflare-tunnel-warp-2026-guide-53n7</link>
      <guid>https://dev.to/orthogonalinfo/build-a-free-vpn-with-cloudflare-tunnel-warp-2026-guide-53n7</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Cloudflare offers two free VPN solutions: &lt;strong&gt;WARP&lt;/strong&gt; (consumer privacy VPN using WireGuard) and &lt;strong&gt;Cloudflare Tunnel + Zero Trust&lt;/strong&gt; (self-hosted VPN replacement for accessing your home network). This guide covers both approaches step-by-step, with Docker Compose configs, split-tunnel setup, and &lt;a href="https://orthogonal.info/master-docker-container-security-best-practices-for-2026/" rel="noopener noreferrer"&gt;security hardening&lt;/a&gt;. Zero Trust is free for up to 50 users — enough for any homelab or small team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Build Your Own VPN in 2026?
&lt;/h2&gt;

&lt;p&gt;Commercial VPN providers make bold promises about privacy, but their centralized architecture creates a fundamental trust problem. You’re routing &lt;em&gt;all&lt;/em&gt; your traffic through servers you don’t control, operated by companies whose revenue model depends on subscriber volume — not security audits. ExpressVPN, NordVPN, and Surfshark have all faced scrutiny over logging practices, jurisdiction shopping, and opaque ownership structures.&lt;/p&gt;

&lt;p&gt;Cloudflare offers a different model. Instead of renting someone else’s VPN, you build your own using Cloudflare’s global Anycast network (330+ data centers in 120+ countries) as the transport layer. The result is a VPN that’s &lt;strong&gt;faster than most commercial alternatives&lt;/strong&gt;, costs nothing, and gives you full control over access policies.&lt;/p&gt;

&lt;p&gt;There are two distinct approaches, and you might want both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cloudflare WARP&lt;/strong&gt; — A consumer VPN app that encrypts your device traffic using WireGuard. Install, toggle on, done. Best for: browsing privacy on public Wi-Fi.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cloudflare Tunnel + Zero Trust&lt;/strong&gt; — A self-hosted VPN replacement that lets you &lt;a href="https://orthogonal.info/secure-remote-access-for-your-homelab/" rel="noopener noreferrer"&gt;access your home network&lt;/a&gt; (NAS, Proxmox, Pi-hole, Docker services) from anywhere without opening a single firewall port. Best for: homelabbers, remote workers, small teams.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 1: Cloudflare WARP — The 5-Minute Privacy VPN
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What WARP Actually Does
&lt;/h3&gt;

&lt;p&gt;WARP is built on the &lt;strong&gt;WireGuard protocol&lt;/strong&gt; — the same modern, lightweight VPN protocol that replaced IPSec and OpenVPN in most serious deployments. When you enable WARP, your device establishes an encrypted tunnel to the nearest Cloudflare data center. From there, your traffic exits onto the internet through Cloudflare’s network.&lt;/p&gt;

&lt;p&gt;Key technical details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Protocol&lt;/strong&gt;: WireGuard (via Cloudflare’s BoringTun implementation in Rust)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DNS&lt;/strong&gt;: Queries routed through 1.1.1.1 (Cloudflare’s privacy-first DNS resolver, audited by KPMG)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Encryption&lt;/strong&gt;: ChaCha20-Poly1305 for data, Curve25519 for key exchange&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Latency impact&lt;/strong&gt;: Typically 1-5ms added (vs. 20-50ms for most commercial VPNs) because traffic routes to the nearest Anycast PoP&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No IP selection&lt;/strong&gt;: WARP doesn’t let you choose exit countries — it’s a privacy tool, not a geo-unblocking tool&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;WARP runs on every major platform through the &lt;strong&gt;1.1.1.1 app&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Platform&lt;br&gt;
Install Method&lt;/p&gt;

&lt;p&gt;Windows&lt;br&gt;
&lt;a href="https://one.one.one.one/" rel="noopener noreferrer"&gt;one.one.one.one&lt;/a&gt; → Download&lt;/p&gt;

&lt;p&gt;macOS&lt;br&gt;
&lt;a href="https://one.one.one.one/" rel="noopener noreferrer"&gt;one.one.one.one&lt;/a&gt; → Download&lt;/p&gt;

&lt;p&gt;iOS&lt;br&gt;
App Store → search “1.1.1.1”&lt;/p&gt;

&lt;p&gt;Android&lt;br&gt;
Play Store → search “1.1.1.1”&lt;/p&gt;

&lt;p&gt;Linux&lt;br&gt;
&lt;code&gt;curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-archive-keyring.gpg &amp;amp;&amp;amp; echo "deb [signed-by=/usr/share/keyrings/cloudflare-archive-keyring.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list &amp;amp;&amp;amp; sudo apt update &amp;amp;&amp;amp; sudo apt install cloudflare-warp&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After installing, launch the app and toggle WARP on. That’s it. Your DNS queries now go through 1.1.1.1 and your traffic is encrypted to Cloudflare’s edge.&lt;/p&gt;
&lt;h3&gt;
  
  
  WARP vs. WARP+ vs. Zero Trust
&lt;/h3&gt;

&lt;p&gt;Feature&lt;br&gt;
WARP (Free)&lt;br&gt;
WARP+ ($)&lt;br&gt;
Zero Trust WARP&lt;/p&gt;

&lt;p&gt;Price&lt;br&gt;
$0&lt;br&gt;
~$5/month&lt;br&gt;
Free (50 users)&lt;/p&gt;

&lt;p&gt;Encryption&lt;br&gt;
WireGuard&lt;br&gt;
WireGuard&lt;br&gt;
WireGuard&lt;/p&gt;

&lt;p&gt;Speed optimization&lt;br&gt;
Standard routing&lt;br&gt;
Argo Smart Routing&lt;br&gt;
Standard routing&lt;/p&gt;

&lt;p&gt;Private network access&lt;br&gt;
No&lt;br&gt;
No&lt;br&gt;
Yes&lt;/p&gt;

&lt;p&gt;Access policies&lt;br&gt;
No&lt;br&gt;
No&lt;br&gt;
Full ZTNA&lt;/p&gt;

&lt;p&gt;DNS filtering&lt;br&gt;
No&lt;br&gt;
No&lt;br&gt;
Gateway policies&lt;/p&gt;

&lt;p&gt;For most people, free WARP is sufficient for everyday privacy. If you need remote access to your homelab, keep reading — Part 2 is where it gets interesting.&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 2: Cloudflare Tunnel + Zero Trust — The Self-Hosted VPN Replacement
&lt;/h2&gt;

&lt;p&gt;This is the setup that replaces WireGuard, OpenVPN, or Tailscale for accessing your home network. The architecture is elegant: a lightweight daemon called &lt;code&gt;cloudflared&lt;/code&gt; runs inside your network and maintains an outbound-only encrypted tunnel to Cloudflare. Remote clients connect through Cloudflare’s network using the WARP client. No inbound ports. No dynamic DNS. No exposed IP address.&lt;/p&gt;
&lt;h3&gt;
  
  
  Architecture Overview
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;br&gt;
javascript&lt;br&gt;
&lt;code&gt;┌─────────────────┐         ┌──────────────────────┐         ┌─────────────────┐&lt;br&gt;
│  Remote Device  │         │   Cloudflare Edge    │         │  Home Network   │&lt;br&gt;
│  (WARP Client)  │◄───────►│   330+ PoPs globally │◄───────►│  (cloudflared)  │&lt;br&gt;
│                 │  WireGuard│                      │ Outbound │                 │&lt;br&gt;
│  Phone/Laptop   │  Tunnel  │  Zero Trust Policies │  Tunnel  │  NAS/Docker/LAN │&lt;br&gt;
└─────────────────┘         └──────────────────────┘         └─────────────────┘&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
### Prerequisites

- A **Cloudflare account** (free tier works)

- A **domain name** with DNS managed by Cloudflare (required for tunnel management)

- A server on your home network — any Linux box, Raspberry Pi, Synology NAS, or even a Docker container on TrueNAS

- **Docker + Docker Compose** (recommended) or bare-metal `cloudflared` installation

### Step 1: Create a Tunnel in the Zero Trust Dashboard

- Go to [one.dash.cloudflare.com](https://one.dash.cloudflare.com/) → Networks → Tunnels

- Click **Create a tunnel**

- Select **Cloudflared** as the connector type

- Name your tunnel (e.g., `homelab-tunnel`)

- Copy the **tunnel token** — you’ll need this for the Docker config

### Step 2: Deploy cloudflared with Docker Compose

Create a `docker-compose.yml` on your home server:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
sql&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="nv"&gt;`version: "3.8"
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared-tunnel
    restart: unless-stopped
    command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
    environment:
      - TUNNEL_TOKEN=${TUNNEL_TOKEN}
    network_mode: host   # Required for private network routing

  # Example: expose a local service
  whoami:
    image: traefik/whoami
    container_name: whoami
    ports:
      - "8080:80"`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file alongside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`TUNNEL_TOKEN=eyJhIjoiYWJj...your-token-here`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the tunnel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`docker compose up -d
docker logs cloudflared-tunnel  # Should show "Connection registered"`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical note&lt;/strong&gt;: Use &lt;code&gt;network_mode: host&lt;/code&gt; if you want to route traffic to your entire LAN subnet (192.168.x.0/24). Without it, cloudflared can only reach services within the Docker network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Expose Services via Public Hostnames
&lt;/h3&gt;

&lt;p&gt;Back in the Zero Trust dashboard, under your tunnel’s &lt;strong&gt;Public Hostnames&lt;/strong&gt; tab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Add a public hostname&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set subdomain: &lt;code&gt;nas&lt;/code&gt;, domain: &lt;code&gt;yourdomain.com&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Service type: &lt;code&gt;HTTP&lt;/code&gt;, URL: &lt;code&gt;localhost:5000&lt;/code&gt; (or wherever your service runs)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cloudflare automatically creates a DNS record. Your NAS is now accessible at &lt;code&gt;https://nas.yourdomain.com&lt;/code&gt; — with automatic SSL, DDoS protection, and Cloudflare WAF.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Enable Private Network Routing (Full VPN Mode)
&lt;/h3&gt;

&lt;p&gt;This is what turns a simple tunnel into a full VPN replacement. Instead of exposing individual services, you route an entire IP subnet through the tunnel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In Zero Trust dashboard → Networks → Tunnels → your tunnel → &lt;strong&gt;Private Networks&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add your LAN CIDR: &lt;code&gt;10.0.0.x/24&lt;/code&gt; (adjust to your subnet)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to Settings → WARP Client → &lt;strong&gt;Split Tunnels&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Switch to &lt;strong&gt;Include mode&lt;/strong&gt; and add &lt;code&gt;10.0.0.x/24&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, any device running the WARP client (enrolled in your Zero Trust org) can access &lt;code&gt;192.168.1.x&lt;/code&gt; addresses as if they were on your home network. SSH into your server, access your NAS web UI, reach your Pi-hole dashboard — all without port forwarding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Enroll Client Devices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Install the &lt;strong&gt;1.1.1.1 / WARP&lt;/strong&gt; app on your phone or laptop&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to Settings → Account → Login to Cloudflare Zero Trust&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter your team name (set during Zero Trust setup)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authenticate with the method you configured (email OTP, Google SSO, GitHub, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable &lt;strong&gt;Gateway with WARP&lt;/strong&gt; mode&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test it: connect to mobile data (not your home Wi-Fi) and try accessing a LAN IP like &lt;code&gt;http://10.0.0.x&lt;/code&gt;. If the router admin page loads, your VPN is working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Lock It Down — Zero Trust Access Policies
&lt;/h2&gt;

&lt;p&gt;The “Zero Trust” part of this setup is what separates it from a traditional VPN. Instead of “anyone with the VPN key gets full network access,” you define granular policies:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
bash&lt;br&gt;
`Zero Trust Dashboard → Access → Applications → Add an Application&lt;/p&gt;

&lt;p&gt;Application type: Self-hosted&lt;br&gt;
Application domain: nas.yourdomain.com&lt;/p&gt;

&lt;p&gt;Policy: Allow&lt;br&gt;
Include: Emails ending in @yourdomain.com&lt;br&gt;
Require: Country equals United States (optional geo-fence)&lt;/p&gt;

&lt;p&gt;Session duration: 24 hours`&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
You can create different policies per service. Your Proxmox admin panel might require hardware key (FIDO2) authentication, while your Jellyfin media server only needs email OTP. This is **Zero Trust Network Access (ZTNA)** — the same architecture that Google BeyondCorp and Microsoft Entra use internally.

## Cloudflare Tunnel vs. Alternatives: Honest Comparison

Feature
Cloudflare Tunnel
WireGuard
Tailscale
OpenVPN

Price
Free (50 users)
Free
Free (100 devices)
Free

Open ports required
None
1 UDP port
None
1 UDP/TCP port

Setup complexity
Medium
Medium-High
Low
High

Works behind CG-NAT
Yes
Needs port forward
Yes
Needs port forward

Access control
Full ZTNA policies
Key-based only
ACLs + SSO
Cert-based

DDoS protection
Yes (Cloudflare)
No
No
No

SSL/TLS termination
Automatic
N/A
N/A
Manual

Trust model
Trust Cloudflare
Self-hosted
Trust Tailscale
Self-hosted

Best for
Web services + LAN
Pure privacy
Mesh networking
Enterprise legacy

**The honest tradeoff**: Cloudflare Tunnel routes your traffic through Cloudflare’s infrastructure. If you fundamentally distrust any third party touching your packets, self-hosted WireGuard is the purist choice. But for most homelabbers, the convenience of zero open ports + free DDoS protection + granular access policies makes Cloudflare Tunnel the pragmatic winner.

## Advanced: Multi-Service Docker Stack

Here’s a production-grade Docker Compose that exposes multiple services through a single tunnel:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
sql&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="nv"&gt;`version: "3.8"

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
    environment:
      - TUNNEL_TOKEN=${TUNNEL_TOKEN}
    networks:
      - tunnel
    depends_on:
      - nginx

  nginx:
    image: nginx:alpine
    container_name: nginx-proxy
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    networks:
      - tunnel

  # Add your services here — they just need to be on the 'tunnel' network
  # Configure public hostnames in the CF dashboard to point to nginx

networks:
  tunnel:
    name: cf-tunnel`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Map each service to a subdomain in the Zero Trust dashboard: &lt;code&gt;grafana.yourdomain.com → http://nginx:3000&lt;/code&gt;, &lt;code&gt;code.yourdomain.com → http://nginx:8443&lt;/code&gt;, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tunnel shows “Disconnected” in the dashboard
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Check Docker logs: &lt;code&gt;docker logs cloudflared-tunnel&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify your token hasn’t been rotated&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure outbound HTTPS (port 443) isn’t blocked by your router/ISP&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If behind a corporate firewall, &lt;code&gt;cloudflared&lt;/code&gt; also supports HTTP/2 over port 7844&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Private network routing doesn’t work
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Confirm &lt;code&gt;network_mode: host&lt;/code&gt; in Docker Compose (or use macvlan)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check that the CIDR in “Private Networks” matches your actual subnet&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify Split Tunnels are set to &lt;strong&gt;Include&lt;/strong&gt; mode (not Exclude)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the client, run &lt;code&gt;warp-cli settings&lt;/code&gt; to verify the private routes are active&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  WARP client won’t enroll
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Double-check your team name in Zero Trust → Settings → Custom Pages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure you’ve created a &lt;strong&gt;Device enrollment policy&lt;/strong&gt; under Settings → WARP Client → Device enrollment permissions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Allow email domains or specific emails that can enroll&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security Hardening Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;☐ Enable &lt;strong&gt;Require Gateway&lt;/strong&gt; in device enrollment — forces all enrolled devices through Cloudflare Gateway for DNS filtering&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;☐ Set session duration to 24h or less for sensitive services&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;☐ Require &lt;strong&gt;FIDO2/hardware keys&lt;/strong&gt; for admin panels (Proxmox, router, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;☐ Enable &lt;strong&gt;device posture checks&lt;/strong&gt;: require screen lock, OS version, disk encryption&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;☐ Use &lt;strong&gt;Service Tokens&lt;/strong&gt; (not user auth) for machine-to-machine tunnel access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;☐ Monitor Access audit logs: Zero Trust → Logs → Access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;☐ Never put your tunnel token in a public Git repository — use &lt;code&gt;.env&lt;/code&gt; files and &lt;code&gt;.gitignore&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;☐ Rotate tunnel tokens periodically via the dashboard&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Is Cloudflare Tunnel really free?
&lt;/h3&gt;

&lt;p&gt;Yes. Cloudflare Zero Trust offers a free plan that includes tunnels, access policies, and WARP client enrollment for up to 50 users. There are no bandwidth limits on the free tier. Paid plans (starting at $7/user/month) add features like logpush, extended session management, and dedicated egress IPs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Cloudflare see my traffic?
&lt;/h3&gt;

&lt;p&gt;Cloudflare terminates TLS at their edge, so they technically &lt;em&gt;could&lt;/em&gt; inspect unencrypted HTTP traffic passing through the tunnel. For HTTPS services, end-to-end encryption between your browser and origin server means Cloudflare sees metadata (domain, timing) but not content. If this is a concern, use WireGuard for a fully self-hosted solution where no third party touches your packets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does this work with Starlink / CG-NAT / mobile hotspots?
&lt;/h3&gt;

&lt;p&gt;Yes — this is one of Cloudflare Tunnel’s biggest advantages. Since the tunnel is outbound-only, it works behind any NAT, including carrier-grade NAT (CG-NAT) used by Starlink, T-Mobile Home Internet, and most 4G/5G connections. No port forwarding needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use this for site-to-site VPN?
&lt;/h3&gt;

&lt;p&gt;Yes, using &lt;strong&gt;WARP Connector&lt;/strong&gt; (currently in beta). Install cloudflared with WARP Connector mode on a device at each site, and Cloudflare routes traffic between subnets. This replaces traditional IPSec site-to-site tunnels.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloudflare Tunnel vs. Tailscale — which should I use?
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;Tailscale&lt;/strong&gt; if your primary need is device-to-device mesh networking (see also our guide on &lt;a href="https://orthogonal.info/home-network-segmentation-with-opnsense/" rel="noopener noreferrer"&gt;home network segmentation with OPNsense&lt;/a&gt;) (accessing any device from any other device). Use &lt;strong&gt;Cloudflare Tunnel&lt;/strong&gt; if you want to expose web services with automatic HTTPS and DDoS protection, or if you need granular &lt;a href="https://orthogonal.info/zero-trust-for-developers-simplifying-security/" rel="noopener noreferrer"&gt;ZTNA policies&lt;/a&gt;. Many homelabbers use both: Tailscale for device mesh, Cloudflare Tunnel for public-facing services.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cloudflare. “&lt;a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" rel="noopener noreferrer"&gt;Connect private networks&lt;/a&gt;.” &lt;em&gt;Cloudflare One Documentation&lt;/em&gt;, 2026.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cloudflare. “&lt;a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/" rel="noopener noreferrer"&gt;Cloudflare Tunnel setup guide&lt;/a&gt;.” &lt;em&gt;Cloudflare Developers&lt;/em&gt;, 2026.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Donenfeld, Jason A. “&lt;a href="https://www.wireguard.com/papers/wireguard.pdf" rel="noopener noreferrer"&gt;WireGuard: Next Generation Kernel Network Tunnel&lt;/a&gt;.” &lt;em&gt;NDSS Symposium&lt;/em&gt;, 2017.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cloudflare. “&lt;a href="https://blog.cloudflare.com/1111-warp-better-vpn/" rel="noopener noreferrer"&gt;Introducing WARP: Fixing Mobile Internet Performance and Security&lt;/a&gt;.” &lt;em&gt;Cloudflare Blog&lt;/em&gt;, 2019.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ward, Brendan and Harris, Rustam. “&lt;a href="https://blog.cloudflare.com/boringtun-userspace-wireguard-rust/" rel="noopener noreferrer"&gt;BoringTun: a userspace WireGuard implementation in Rust&lt;/a&gt;.” &lt;em&gt;Cloudflare Blog&lt;/em&gt;, 2019.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google. “&lt;a href="https://cloud.google.com/beyondcorp" rel="noopener noreferrer"&gt;BeyondCorp: A New Approach to Enterprise Security&lt;/a&gt;.” &lt;em&gt;Google Cloud&lt;/em&gt;, 2014.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
      <category>docker</category>
    </item>
    <item>
      <title>Pod Security Standards: A Security-First Guide</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Thu, 09 Apr 2026 16:49:44 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/pod-security-standards-a-security-first-guide-33ln</link>
      <guid>https://dev.to/orthogonalinfo/pod-security-standards-a-security-first-guide-33ln</guid>
      <description>&lt;h2&gt;
  
  
  Kubernetes Pod Security Standards
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I enforce PSS &lt;code&gt;restricted&lt;/code&gt; on all production namespaces: &lt;code&gt;runAsNonRoot: true&lt;/code&gt;, &lt;code&gt;allowPrivilegeEscalation: false&lt;/code&gt;, all capabilities dropped, read-only root filesystem. Start with &lt;code&gt;warn&lt;/code&gt; mode to find violations, then switch to &lt;code&gt;enforce&lt;/code&gt;. This single change blocks the majority of container escape attacks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine this: your Kubernetes cluster is humming along nicely, handling thousands of requests per second. Then, out of nowhere, you discover that one of your pods has been compromised. The attacker exploited a misconfigured pod to escalate privileges and access sensitive data. If this scenario sends chills down your spine, you're not alone. Kubernetes security is a moving target, and Pod Security Standards (PSS) are here to help.&lt;/p&gt;

&lt;p&gt;PSS is Kubernetes' answer to the growing need for solid, declarative security policies. They provide a framework for defining and enforcing security requirements for pods, ensuring that your workloads adhere to best practices. But PSS isn't just about ticking compliance checkboxes — it's about aligning security with DevSecOps principles, where security is baked into every stage of the development lifecycle.&lt;/p&gt;

&lt;p&gt;From PodSecurityPolicy (deprecated in Kubernetes 1.21) to Pod Security Standards, the focus has shifted toward simplicity and usability. PSS is designed to be developer-friendly while still offering powerful controls to secure your workloads.&lt;/p&gt;

&lt;p&gt;At its core, PSS enables teams to adopt a "security-first" mindset — protecting your cluster from external threats while mitigating risks posed by internal misconfigurations. By enforcing security policies at the namespace level, PSS ensures that every pod deployed adheres to predefined standards.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;From experience:&lt;/strong&gt; Run &lt;code&gt;kubectl label ns YOUR_NAMESPACE pod-security.kubernetes.io/warn=restricted&lt;/code&gt; first. This logs warnings without blocking deployments. Review the warnings for 1-2 weeks, fix the pod specs, then switch to &lt;code&gt;enforce&lt;/code&gt;. I've migrated clusters with 100+ namespaces using this process with zero downtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Key Challenges in Securing Kubernetes Pods
&lt;/h2&gt;

&lt;p&gt;Securing Kubernetes pods is easier said than done. Pods are the atomic unit of Kubernetes, and their configurations can be a goldmine for attackers if not properly secured. Common vulnerabilities include overly permissive access controls, unbounded resource limits, and insecure container images.&lt;/p&gt;

&lt;p&gt;The core tension: developers want their pods to "just work," and adding &lt;code&gt;runAsNonRoot: true&lt;/code&gt; or dropping capabilities breaks applications that assume root access. I've seen teams disable PSS entirely because one service needed &lt;code&gt;NET_BIND_SERVICE&lt;/code&gt;. The fix isn't to weaken the policy — it's to grant targeted exceptions via a namespace with &lt;code&gt;Baseline&lt;/code&gt; level for that specific workload, while keeping &lt;code&gt;Restricted&lt;/code&gt; everywhere else.&lt;/p&gt;

&lt;p&gt;Remember the Tesla Kubernetes breach in 2018? Attackers exploited a misconfigured pod to mine cryptocurrency. The pod had access to sensitive credentials stored in environment variables, and the cluster lacked proper monitoring. This incident underscores the importance of securing pod configurations from the outset.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Common Pitfall:&lt;/strong&gt; Ignoring resource limits in pod configurations can lead to denial-of-service attacks. Always define &lt;code&gt;resources.limits&lt;/code&gt; and &lt;code&gt;resources.requests&lt;/code&gt; in your pod manifests to prevent resource exhaustion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Implementing Pod Security Standards in Production
&lt;/h2&gt;

&lt;p&gt;How do you implement Pod Security Standards effectively? Here's the breakdown:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Understand the PSS levels:&lt;/strong&gt; Kubernetes defines three levels — &lt;code&gt;Privileged&lt;/code&gt;, &lt;code&gt;Baseline&lt;/code&gt;, and &lt;code&gt;Restricted&lt;/code&gt;. Each represents a stricter set of security controls.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apply labels to namespaces:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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;v1&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;Namespace&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;secure-apps&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pod-security.kubernetes.io/enforce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restricted&lt;/span&gt;
    &lt;span class="na"&gt;pod-security.kubernetes.io/audit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;baseline&lt;/span&gt;
    &lt;span class="na"&gt;pod-security.kubernetes.io/warn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;baseline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audit and monitor:&lt;/strong&gt; Use Kubernetes audit logs to monitor compliance. The &lt;code&gt;audit&lt;/code&gt; and &lt;code&gt;warn&lt;/code&gt; labels identify pods that violate policies without blocking them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supplement with OPA/Gatekeeper:&lt;/strong&gt; PSS covers the basics, but you'll need Gatekeeper for custom policies like "no images from Docker Hub" or "all pods must have resource limits." I run 12 custom Gatekeeper constraints on top of PSS.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;My migration path:&lt;/strong&gt; Week 1: apply &lt;code&gt;warn=restricted&lt;/code&gt; to all production namespaces. Week 2: collect and triage warnings. Week 3: move fixed namespaces to &lt;code&gt;enforce=restricted&lt;/code&gt;, exceptions to &lt;code&gt;enforce=baseline&lt;/code&gt;. Week 4: add CI validation with &lt;code&gt;kube-score&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For dev namespaces, I use &lt;code&gt;enforce=baseline&lt;/code&gt; (not &lt;code&gt;privileged&lt;/code&gt;). Even in dev, you want to catch the most dangerous misconfigurations.&lt;/p&gt;

&lt;p&gt;CI integration is non-negotiable: run &lt;code&gt;kubectl --dry-run=server&lt;/code&gt; against a namespace with &lt;code&gt;enforce=restricted&lt;/code&gt; in your pipeline. If the manifest would be rejected, fail the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Battle-Tested Strategies
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integrate PSS into CI/CD pipelines:&lt;/strong&gt; Shift security left by validating pod configurations during build. Tools like &lt;code&gt;kube-score&lt;/code&gt; and &lt;code&gt;kubesec&lt;/code&gt; analyze your manifests for security risks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor pod activity:&lt;/strong&gt; Use Falco to detect suspicious activity in real-time — shell commands, sensitive file access, unexpected network connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit permissions:&lt;/strong&gt; Follow least privilege. Avoid running pods as root and restrict access to sensitive resources using RBAC.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use network policies to control traffic between pods:&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;networking.k8s.io/v1&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;NetworkPolicy&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;restrict-traffic&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;secure-apps&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;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;policyTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Egress&lt;/span&gt;
  &lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trusted-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Real incident:&lt;/strong&gt; I've audited clusters where every pod was running as root with all capabilities because nobody set a SecurityContext. The default is insecure. PSS &lt;code&gt;Restricted&lt;/code&gt; mode makes the secure configuration the default, not the exception.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Future Trends
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Emerging security features:&lt;/strong&gt; Kubernetes is introducing ephemeral containers and runtime security profiles to enhance pod security, reducing attack surfaces and improving isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;seccomp and AppArmor profiles&lt;/strong&gt; are graduating from beta. I'm already running custom seccomp profiles that restrict system calls per workload type — web servers get a different profile than batch processors. This is the next layer beyond PSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strengthening Security with RBAC
&lt;/h2&gt;

&lt;p&gt;RBAC is a cornerstone of Kubernetes security. Define roles and bind them to users or service accounts to control access:&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;rbac.authorization.k8s.io/v1&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;Role&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;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secure-apps&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;pod-reader&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&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;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&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;pods"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;verbs&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;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;From experience:&lt;/strong&gt; Run &lt;code&gt;kubectl auth can-i --list --as=system:serviceaccount:NAMESPACE:default&lt;/code&gt; for every namespace. If the default ServiceAccount can list secrets or create pods, you have a problem. I strip all permissions from default ServiceAccounts and create dedicated ones per workload.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pod Security Standards provide a declarative way to enforce security policies in Kubernetes&lt;/li&gt;
&lt;li&gt;Common pod vulnerabilities include excessive permissions, insecure images, and unbounded resource limits&lt;/li&gt;
&lt;li&gt;Use OPA, Gatekeeper, and Falco to automate enforcement and monitoring&lt;/li&gt;
&lt;li&gt;Integrate PSS into CI/CD pipelines to shift security left&lt;/li&gt;
&lt;li&gt;Start with &lt;code&gt;warn&lt;/code&gt; mode, then move to &lt;code&gt;enforce&lt;/code&gt; — never skip the audit phase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have you implemented Pod Security Standards in your clusters? Share your experiences or horror stories — I'd love to hear them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://orthogonal.info/pod-security-standards-a-security-first-guide/" rel="noopener noreferrer"&gt;orthogonal.info&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Google Play 12-Tester Wall: A Solo Dev's Guide (and a Plea for Help)</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Tue, 07 Apr 2026 18:41:50 +0000</pubDate>
      <link>https://dev.to/orthogonalinfo/the-google-play-12-tester-wall-a-solo-devs-guide-and-a-plea-for-help-4o10</link>
      <guid>https://dev.to/orthogonalinfo/the-google-play-12-tester-wall-a-solo-devs-guide-and-a-plea-for-help-4o10</guid>
      <description>&lt;h2&gt;
  
  
  The Problem Every Solo Dev Hits
&lt;/h2&gt;

&lt;p&gt;You build your app. You polish it. You upload it to Google Play Console. And then... you discover the &lt;strong&gt;12-tester requirement&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Google requires new developer accounts to have &lt;strong&gt;at least 12 unique testers opted into your internal testing track for 14 continuous days&lt;/strong&gt; before you can publish to production. For big companies with QA teams, this is nothing. For solo devs? It's a wall.&lt;/p&gt;

&lt;p&gt;I'm currently stuck behind that wall with two apps and just &lt;strong&gt;1 tester&lt;/strong&gt; (myself). I've been stuck for two weeks.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  FocusForge 🎯
&lt;/h3&gt;

&lt;p&gt;A minimal focus timer. No account required, no cloud sync, no ads. Just a clean Pomodoro-style timer that tracks your deep work sessions locally. I built it because every other focus app wanted me to create an account and pay $5/month for what is essentially a countdown timer.&lt;/p&gt;

&lt;h3&gt;
  
  
  NoiseLog 🔊
&lt;/h3&gt;

&lt;p&gt;Measures and logs ambient noise levels using your phone's microphone. I originally built this to document a noisy neighbor situation, but it turned out useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finding the quietest seat in coffee shops&lt;/li&gt;
&lt;li&gt;Tracking if construction noise exceeds legal limits&lt;/li&gt;
&lt;li&gt;Understanding your daily noise exposure patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both apps are &lt;strong&gt;free, no ads, no data collection, no weird permissions&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm Asking
&lt;/h2&gt;

&lt;p&gt;If you have an Android phone (any recent version), joining takes about 60 seconds:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click one of the internal testing links below&lt;/li&gt;
&lt;li&gt;Sign in with your Google account&lt;/li&gt;
&lt;li&gt;Install the app from the Play Store page&lt;/li&gt;
&lt;li&gt;Keep it installed for ~14 days&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. You don't even need to actively use it (though feedback is welcome). You're just helping me get past Google's gatekeeper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Join links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://play.google.com/apps/internaltest/com.orthogonal.focusforge" rel="noopener noreferrer"&gt;FocusForge internal test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://play.google.com/apps/internaltest/com.orthogonal.noiselog" rel="noopener noreferrer"&gt;NoiseLog internal test&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tips for Other Devs in the Same Boat
&lt;/h2&gt;

&lt;p&gt;If you're also stuck at the 12-tester wall, here's what I've learned:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start recruiting testers early&lt;/strong&gt; — don't wait until your app is "ready." The 14-day clock only starts when people opt in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Family and friends are your fastest path&lt;/strong&gt; — but many won't have Android.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;r/betatesting and r/playmyapp&lt;/strong&gt; on Reddit are purpose-built for this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FeatureGate.de&lt;/strong&gt; is a free mutual-testing platform — test 3 apps, earn the right to post your own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TestersCommunity.com&lt;/strong&gt; charges $15 for 25 testers with a production access guarantee if you want the fast track.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev.to and IndieHackers&lt;/strong&gt; — you're reading this, so you know these communities exist. Post your story.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The requirement exists to filter spam, and I get that. But it's one of those things where the cure is worse than the disease for legitimate solo devs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Help a Dev Out
&lt;/h2&gt;

&lt;p&gt;If you joined one of the tests above: &lt;strong&gt;thank you&lt;/strong&gt;. Seriously. Every tester gets me one step closer to actually shipping these apps.&lt;/p&gt;

&lt;p&gt;If you've been through the same struggle, drop a comment — I'd love to hear how you solved it.&lt;/p&gt;

</description>
      <category>android</category>
      <category>googleplay</category>
      <category>mobile</category>
      <category>indiedev</category>
    </item>
  </channel>
</rss>
