Files
the-other-dude/docs/website/blog/free-tier-cap.html

353 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Free Is Now Capped at 250 Devices — The Other Dude Blog</title>
<meta name="description" content="The free tier is now limited to 250 managed devices. Here's why, what it means, and what hasn't changed.">
<meta name="keywords" content="MikroTik, fleet management, licensing, BSL, free tier, The Other Dude">
<meta name="author" content="The Other Dude">
<meta name="robots" content="index, follow">
<meta name="theme-color" content="#eae7de">
<link rel="canonical" href="https://theotherdude.net/blog/free-tier-cap.html">
<link rel="icon" href="../data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><rect x='2' y='2' width='60' height='60' rx='8' fill='none' stroke='%238B1A1A' stroke-width='2'/><path d='M32 18 L46 32 L32 46 L18 32 Z' fill='%238B1A1A'/><path d='M32 19 L38 32 L32 45 L26 32 Z' fill='%232A9D8F'/><circle cx='32' cy='32' r='5' fill='%238B1A1A'/><circle cx='32' cy='32' r='2.5' fill='%232A9D8F'/></svg>">
<!-- Open Graph -->
<meta property="og:type" content="article">
<meta property="og:title" content="Free Is Now Capped at 250 Devices — The Other Dude">
<meta property="og:description" content="The free tier is now limited to 250 managed devices. Here's why, what it means, and what hasn't changed.">
<meta property="og:url" content="https://theotherdude.net/blog/free-tier-cap.html">
<meta property="og:site_name" content="The Other Dude">
<meta property="article:published_time" content="2026-03-19">
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Free Is Now Capped at 250 Devices",
"description": "The free tier is now limited to 250 managed devices. Here's why, what it means, and what hasn't changed.",
"datePublished": "2026-03-19",
"author": {
"@type": "Organization",
"name": "The Other Dude"
},
"publisher": {
"@type": "Organization",
"name": "The Other Dude",
"url": "https://theotherdude.net"
},
"mainEntityOfPage": "https://theotherdude.net/blog/free-tier-cap.html"
}
</script>
<!-- Fonts -->
<link rel="stylesheet" href="../style.css?v=3">
<style>
/* Warm Precision overrides */
:root {
--background: #eae7de;
--surface: #f6f4ec;
--elevated: #f0ede4;
--border: rgba(40,36,28,0.12);
--text-primary: #1a1810;
--text-secondary: #5e5a4e;
--text-muted: #8a8578;
--accent: #8a7a48;
}
body { background-color: #eae7de; color: #1a1810; }
.site-nav { background: #e0dcd2 !important; border-bottom: 1px solid rgba(40,36,28,0.12); }
.site-nav .nav-logo span, .site-nav .nav-link, .site-nav .nav-cta { color: #1a1810 !important; }
.site-nav .nav-link:hover { color: #8a7a48 !important; }
.dark { /* prevent dark mode */ }
</style>
<style>
.blog-post {
max-width: 720px;
margin: 0 auto;
padding: 80px 24px 120px;
}
.blog-post-meta {
color: var(--text-muted);
font-size: 14px;
margin-bottom: 8px;
}
.blog-post h1 {
font-family: "Manrope", sans-serif;
font-weight: 700;
font-size: 2.5rem;
line-height: 1.2;
color: var(--text-primary);
margin-bottom: 40px;
}
.blog-post h2 {
font-family: "Manrope", sans-serif;
font-weight: 600;
font-size: 1.4rem;
color: var(--text-primary);
margin-top: 48px;
margin-bottom: 16px;
}
.blog-post p {
color: var(--text-secondary);
font-size: 1.05rem;
line-height: 1.75;
margin-bottom: 20px;
}
.blog-post p strong {
color: var(--text-primary);
}
.blog-post a {
color: var(--accent);
text-decoration: underline;
text-underline-offset: 3px;
}
.blog-post a:hover {
color: var(--text-primary);
}
.blog-post .back-link {
display: inline-block;
margin-bottom: 32px;
font-size: 14px;
text-decoration: none;
color: var(--text-muted);
}
.blog-post .back-link:hover {
color: var(--accent);
}
.blog-post .hero-image {
width: 100%;
max-width: 420px;
display: block;
margin: 0 auto 40px;
border-radius: 2px;
opacity: 0.8;
filter: sepia(10%) saturate(85%) brightness(1.02);
mix-blend-mode: multiply;
}
.blog-post ul {
color: var(--text-secondary);
font-size: 1.05rem;
line-height: 1.75;
margin-bottom: 20px;
padding-left: 24px;
}
.blog-post ul li {
margin-bottom: 6px;
}
.blog-post .blog-footer {
margin-top: 64px;
padding-top: 24px;
border-top: 1px solid var(--border);
font-size: 0.9rem;
color: var(--text-muted);
}
.blog-post .blog-footer a {
color: var(--text-secondary);
}
@media (max-width: 480px) {
.blog-post h1 { font-size: 1.8rem; }
.blog-post { padding: 60px 20px 80px; }
}
</style>
</head>
<body>
<nav class="site-nav">
<div class="nav-inner container">
<a href="../index.html" class="nav-logo">
<svg class="nav-logo-mark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="32" height="32" aria-label="The Other Dude logo">
<rect x="2" y="2" width="60" height="60" rx="8" fill="none" stroke="#8B1A1A" stroke-width="2"/>
<rect x="6" y="6" width="52" height="52" rx="5" fill="none" stroke="#F5E6C8" stroke-width="1.5"/>
<rect x="8" y="8" width="48" height="48" rx="4" fill="#8B1A1A" opacity="0.15"/>
<path d="M32 8 L56 32 L32 56 L8 32 Z" fill="none" stroke="#8B1A1A" stroke-width="2"/>
<path d="M32 13 L51 32 L32 51 L13 32 Z" fill="none" stroke="#F5E6C8" stroke-width="1.5"/>
<path d="M32 18 L46 32 L32 46 L18 32 Z" fill="#8B1A1A"/>
<path d="M32 19 L38 32 L32 45 L26 32 Z" fill="#2A9D8F"/>
<path d="M19 32 L32 26 L45 32 L32 38 Z" fill="#F5E6C8"/>
<circle cx="32" cy="32" r="5" fill="#8B1A1A"/>
<circle cx="32" cy="32" r="2.5" fill="#2A9D8F"/>
<path d="M10 10 L16 10 L10 16 Z" fill="#2A9D8F" opacity="0.7"/>
<path d="M54 10 L54 16 L48 10 Z" fill="#2A9D8F" opacity="0.7"/>
<path d="M10 54 L16 54 L10 48 Z" fill="#2A9D8F" opacity="0.7"/>
<path d="M54 54 L48 54 L54 48 Z" fill="#2A9D8F" opacity="0.7"/>
</svg>
<span>The Other Dude</span>
</a>
<div class="nav-links">
<a href="../docs.html" class="nav-link">Docs</a>
<a href="index.html" class="nav-link">Blog</a>
<a href="https://github.com/staack/the-other-dude" class="nav-link" rel="noopener">GitHub</a>
</div>
</div>
</nav>
<main>
<article class="blog-post">
<a href="index.html" class="back-link">&larr; Back to Blog</a>
<div class="blog-post-meta">March 19, 2026</div>
<h1>Free Is Now Capped at 250 Devices</h1>
<p>Free is now capped at 250 devices.</p>
<p>Yeah, I know.</p>
<h2>Why</h2>
<p>This started as a side project. A thing I built because nothing else existed for managing MikroTik fleets without subscribing to somebody's cloud platform or running software from 2008.</p>
<p>It's not a side project anymore. It has a poller that speaks RouterOS binary protocol across thousands of SSH sessions. It has a backend with row-level security, NATS JetStream pipelines, and TimescaleDB hypertables. It has a frontend with wireless link discovery, sector-organized tower views, and signal trending charts. There's a WinBox-in-the-browser feature that took weeks to get right.</p>
<p>All of that costs time. A lot of time. Time that could be spent doing literally anything else.</p>
<p>So the free tier moved from "basically unlimited" to 250 devices. The license is still <a href="https://github.com/staack/the-other-dude/blob/main/LICENSE">BSL 1.1</a>. It still converts to Apache 2.0 in 2030. The SaaS restriction is still there. The only thing that changed is the number.</p>
<figure style="margin: 48px auto; text-align: center;">
<img src="../assets/lawyer.png" alt="The legal team" class="hero-image" style="margin-bottom: 8px;">
<figcaption style="font-size: 0.8rem; color: var(--text-muted); font-style: italic;">My legal department, mid-negotiation.</figcaption>
</figure>
<h2>The Uncomfortable Part</h2>
<p>I don't like writing this post. The entire philosophy of this project is anti-SaaS nonsense. No per-device pricing. No feature gating. No "contact sales for enterprise." No cloud dependency.</p>
<p>That hasn't changed. But the math has.</p>
<p>Software that doesn't generate revenue eventually stops getting maintained. I've watched it happen to dozens of networking tools over the years. The maintainer burns out, the repo goes quiet, and everyone who depended on it scrambles. I'd rather not be that guy.</p>
<p>So this is the compromise: a number that's high enough to cover homelabbers, small shops, and testing environments, but low enough that real commercial deployments &mdash; MSPs managing client networks, WISPs running tower infrastructure &mdash; fall on the other side of the line.</p>
<h2>What 250 Covers</h2>
<ul>
<li>Your homelab with 12 RouterBoards and a dream</li>
<li>A small office with a couple dozen managed switches and APs</li>
<li>A test environment where you're evaluating the platform</li>
<li>A training deployment for your team</li>
</ul>
<p>If you're running a WISP with three tower sites and 200 CPEs, you're probably fine. If you're an MSP managing 15 client networks with 40 devices each, you're not.</p>
<h2>What Hasn't Changed</h2>
<p><strong>No feature gating.</strong> The free version and the commercial version are the same binary. Same code, same features, same everything. There is no "TOD Pro" with extra tabs unlocked. There is no "upgrade to access wireless link discovery." If it's in the repo, you get it.</p>
<p><strong>No phone-home enforcement.</strong> There's no license server. There's no API call that checks how many devices you have against a cloud database. The cap is enforced locally in the device creation endpoint. If you remove it, that's between you and the license agreement.</p>
<p><strong>No tracking.</strong> I don't know how many devices you have. I don't know if you're over the limit. I don't want to know.</p>
<h2>The Ask</h2>
<p>If you're using this to manage real infrastructure &mdash; client networks, production towers, revenue-generating deployments &mdash; pay for it. Not because I'll come after you. Because it's the right thing to do, and because it keeps the project alive.</p>
<p>If you're just messing around, 250 is more than enough.</p>
<p>Reach out at <a href="mailto:license@theotherdude.net">license@theotherdude.net</a> for commercial licensing. I'm one person, so don't expect a sales deck. Expect a straight answer.</p>
<div class="blog-footer">
<p>Read more: <a href="why-this-exists.html">Why This Exists</a> &middot; <a href="what-you-can-do-today.html">What You Can Do With It Today</a> &middot; <a href="not-stable-software.html">This Is Not Stable Software</a></p>
</div>
</article>
</main>
<footer class="site-footer">
<div class="footer-inner container">
<div class="footer-brand">
<span class="footer-logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="24" height="24" aria-hidden="true" style="vertical-align: middle; margin-right: 8px;">
<rect x="2" y="2" width="60" height="60" rx="8" fill="none" stroke="#8B1A1A" stroke-width="2"/>
<rect x="6" y="6" width="52" height="52" rx="5" fill="none" stroke="#F5E6C8" stroke-width="1.5"/>
<rect x="8" y="8" width="48" height="48" rx="4" fill="#8B1A1A" opacity="0.15"/>
<path d="M32 18 L46 32 L32 46 L18 32 Z" fill="#8B1A1A"/>
<path d="M32 19 L38 32 L32 45 L26 32 Z" fill="#2A9D8F"/>
<path d="M19 32 L32 26 L45 32 L32 38 Z" fill="#F5E6C8"/>
<circle cx="32" cy="32" r="5" fill="#8B1A1A"/>
<circle cx="32" cy="32" r="2.5" fill="#2A9D8F"/>
</svg>
The Other Dude
</span>
<span class="footer-copy">&copy; 2026 The Other Dude. All rights reserved.</span>
</div>
<nav class="footer-links">
<a href="../docs.html">Docs</a>
<a href="index.html">Blog</a>
<a href="https://github.com/staack/the-other-dude" rel="noopener">GitHub</a>
<a href="mailto:license@theotherdude.net">Licensing</a>
</nav>
<p style="margin-top:12px;font-size:0.75em;color:#8a8578;text-align:center;">This site uses self-hosted, cookie-free analytics to measure page views and engagement. No personal data is collected or shared with third parties.</p>
</div>
</footer>
<script>
(function() {
var h = 'https://telemetry.theotherdude.net';
var p = location.pathname;
var t = document.title;
var r = document.referrer;
// Session page count via sessionStorage.
var sc = parseInt(sessionStorage.getItem('_tc_sc') || '0', 10) + 1;
sessionStorage.setItem('_tc_sc', sc);
// UTM params.
var sp = new URLSearchParams(location.search);
var us = sp.get('utm_source') || '';
var um = sp.get('utm_medium') || '';
var uc = sp.get('utm_campaign') || '';
// Pixel URL with all params.
var params = new URLSearchParams({
p: p, t: t, r: r,
sw: screen.width, sh: screen.height,
vw: innerWidth, vh: innerHeight,
tz: new Date().getTimezoneOffset(),
dpr: devicePixelRatio || 1,
touch: navigator.maxTouchPoints > 0 ? 1 : 0,
cd: screen.colorDepth,
plt: Math.round(performance.now()),
sc: sc
});
if (us) params.set('us', us);
if (um) params.set('um', um);
if (uc) params.set('uc', uc);
var ct = navigator.connection ? navigator.connection.effectiveType : '';
if (ct) params.set('ct', ct);
new Image().src = h + '/px?' + params.toString();
// Engagement tracking.
var startTime = performance.now();
var maxScroll = 0;
function getScrollDepth() {
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
var docHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
var winHeight = innerHeight;
if (docHeight <= winHeight) return 100;
var pct = Math.round((scrollTop + winHeight) / docHeight * 100);
return Math.min(pct, 100);
}
window.addEventListener('scroll', function() {
var d = getScrollDepth();
if (d > maxScroll) maxScroll = d;
}, {passive: true});
// Send beacon on page hide.
function sendBeacon() {
var top = Math.round(performance.now() - startTime);
var data = new URLSearchParams({p: p, top: top, sd: maxScroll});
navigator.sendBeacon(h + '/px/beacon', data);
}
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'hidden') sendBeacon();
});
window.addEventListener('pagehide', sendBeacon);
})();
</script>
</body>
</html>