<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Tailscale on Olaf Alders</title><link>https://www.olafalders.com/categories/tailscale/</link><description>Recent content in Tailscale on Olaf Alders</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>olaf@wundersolutions.com (Olaf Alders)</managingEditor><webMaster>olaf@wundersolutions.com (Olaf Alders)</webMaster><copyright>© 2026 Olaf Alders</copyright><lastBuildDate>Tue, 30 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.olafalders.com/categories/tailscale/index.xml" rel="self" type="application/rss+xml"/><item><title>Keep It Local</title><link>https://www.olafalders.com/2026/06/30/keep-it-local/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><author>olaf@wundersolutions.com (Olaf Alders)</author><guid>https://www.olafalders.com/2026/06/30/keep-it-local/</guid><description>A bit of networking: binding an app to localhost (127.0.0.1) differs from binding to 0.0.0.0, and the distinction is important. Let's look at how to keep clodhopper, air, Python's http.server, and http_this either local or on a Tailscale network.</description><content:encoded>&lt;p&gt;Who wants to talk about networking today? I know I do. Two
addresses do most of the heavy lifting here: &lt;code&gt;127.0.0.1&lt;/code&gt; (aka &lt;em&gt;localhost&lt;/em&gt; or the
&lt;em&gt;loopback&lt;/em&gt; address — only your own machine can reach it) and &lt;code&gt;0.0.0.0&lt;/code&gt; (bind to
this and you&amp;rsquo;re reachable on &lt;em&gt;every&lt;/em&gt; network interface). See
&lt;a href="https://en.wikipedia.org/wiki/Localhost"&gt;Localhost&lt;/a&gt; and
&lt;a href="https://en.wikipedia.org/wiki/0.0.0.0#Binding"&gt;0.0.0.0&lt;/a&gt; for a refresher.&lt;/p&gt;
&lt;h2 id="clodhopper"&gt;&lt;a class="heading-anchor" href="#clodhopper" aria-label="Permalink to this section"&gt;#&lt;/a&gt;clodhopper&lt;/h2&gt;
&lt;p&gt;Yesterday I talked about &lt;a href="https://www.olafalders.com/2026/06/29/on-hopping-claudes/"&gt;clodhopper&lt;/a&gt;, my
personal Claude Code dashboard. It collects data from your running agents and
spins up a read-only app to show their status. By default, it
binds to localhost (&lt;code&gt;127.0.0.1&lt;/code&gt;), which means I don&amp;rsquo;t accidentally broadcast my
workflows to the world. I also run it on my Tailscale network, which means I can view it on my
own private network, but the world can&amp;rsquo;t. I was doing this by interpolating the
Tailscale IP in the startup command: &lt;code&gt;clodhopper serve --host &amp;quot;$(tailscale ip -4)&amp;quot;&lt;/code&gt;. Today I added a &lt;code&gt;--tailscale&lt;/code&gt; arg, to make this a touch easier.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Default: loopback only — only this machine can reach it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clodhopper serve
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Don&amp;#39;t do this on an untrusted network — binds every interface&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CLODHOPPER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0 clodhopper serve
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Tailnet only — reachable from your tailnet (subject to ACLs), not the LAN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clodhopper serve --tailscale
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So, that&amp;rsquo;s all fine, but we are now living in a world where anyone can spin up a custom app on their machine and accidentally broadcast it to the world. Is this bad? Not always, but consider the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;your app keeps secrets in &lt;code&gt;.env&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;your app spins up a web server&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.env&lt;/code&gt; somehow ends up in the path that your app is serving&lt;/li&gt;
&lt;li&gt;random bot sniffs out your app and fetches &lt;code&gt;.env&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;now you need to rotate your secrets and you may not even be aware that your secrets are in the hands of a bad actor&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- markdownlint-disable MD003 MD033 MD046 --&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2026/06/30/keep-it-local/featured.jpeg"&gt;&lt;img src="https://www.olafalders.com/2026/06/30/keep-it-local/featured.jpeg" alt="featured"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p class="attribution"&gt;&lt;!-- markdownlint-disable-line --&gt;&amp;quot;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/10768314@N00/4823138926"&gt;Localhost&lt;/a&gt;&amp;quot; by &lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/nitsckie/"&gt;Wesley Nitsckie&lt;/a&gt; is licensed under &lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-sa/2.0/"&gt;CC BY-SA 2.0 &lt;img src="https://www.olafalders.com/img/cc/cc.svg" alt="" style="height: 1em;" /&gt;&lt;img src="https://www.olafalders.com/img/cc/by.svg" alt="" style="height: 1em;" /&gt;&lt;img src="https://www.olafalders.com/img/cc/sa.svg" alt="" style="height: 1em;" /&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ideally you&amp;rsquo;d restrict access to your resources to only the audiences which
require them. So, defaulting to &lt;code&gt;localhost&lt;/code&gt; and then expanding your reach from
there is a good way to go. In my case I&amp;rsquo;ve been enjoying using a
&lt;a href="https://tailscale.com/"&gt;Tailscale&lt;/a&gt; tailnet. Only my own authenticated devices
can connect. Internet creeping will have to take place elsewhere, because my
apps are now for my eyes only.&lt;/p&gt;
&lt;p&gt;Moving beyond &lt;code&gt;clodhopper&lt;/code&gt;, here are ways to apply the same principle to some open source apps.&lt;/p&gt;
&lt;h2 id="air"&gt;&lt;a class="heading-anchor" href="#air" aria-label="Permalink to this section"&gt;#&lt;/a&gt;air&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/air-verse/air"&gt;air&lt;/a&gt; runs &lt;code&gt;full_bin&lt;/code&gt; through a shell, so
you can interpolate &lt;code&gt;tailscale ip -4&lt;/code&gt; straight into your app&amp;rsquo;s host flag in
&lt;code&gt;.air.toml&lt;/code&gt; — no hardcoded address:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Loopback only&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;full_bin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./tmp/main -port 5003 -host 127.0.0.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Tailnet only — resolved at startup, no hardcoded address&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;full_bin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./tmp/main -port 5003 -host $(tailscale ip -4)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="pythons-built-in-file-server"&gt;&lt;a class="heading-anchor" href="#pythons-built-in-file-server" aria-label="Permalink to this section"&gt;#&lt;/a&gt;Python&amp;rsquo;s built-in file server&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http.server&lt;/code&gt; binds &lt;code&gt;0.0.0.0&lt;/code&gt; by default — pass an explicit &lt;code&gt;--bind&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Default binds 0.0.0.0 (all interfaces)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m http.server &lt;span class="m"&gt;5000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Loopback only&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m http.server &lt;span class="m"&gt;5000&lt;/span&gt; --bind 127.0.0.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Tailnet only&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m http.server &lt;span class="m"&gt;5000&lt;/span&gt; --bind &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;tailscale ip -4&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="apphttpthis"&gt;&lt;a class="heading-anchor" href="#apphttpthis" aria-label="Permalink to this section"&gt;#&lt;/a&gt;App::HTTPThis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/dist/App-HTTPThis/view/bin/http_this"&gt;App::HTTPThis&lt;/a&gt;
serves the current directory over HTTP.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;--host&lt;/code&gt; to control the bind address.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Loopback only&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;http_this --host 127.0.0.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Tailnet only&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;http_this --host &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;tailscale ip -4&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;nota bene&lt;/em&gt;: the current version of &lt;code&gt;http_this&lt;/code&gt; binds to &lt;em&gt;every&lt;/em&gt; interface
but emits a message that implies that it is binding &lt;em&gt;only&lt;/em&gt; to &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ http_this .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exporting &lt;span class="s1"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;, available at:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; http://127.0.0.1:7007/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;😬 Today I opened &lt;a href="https://github.com/davorg-cpan/app-httpthis/pull/13"&gt;#13&lt;/a&gt; to
clarify the behaviour, but maybe take this as a reminder that it&amp;rsquo;s good to be
explicit about the things that really matter, rather than relying on the
defaults.&lt;/p&gt;
&lt;h2 id="addendum"&gt;&lt;a class="heading-anchor" href="#addendum" aria-label="Permalink to this section"&gt;#&lt;/a&gt;Addendum&lt;/h2&gt;
&lt;!-- Scaffold — your prose. Sketch notes below to write from: --&gt;
&lt;p&gt;In the days since I originally posted this, &lt;a href="https://github.com/davorg-cpan/app-httpthis/pull/13"&gt;#13&lt;/a&gt; has since been merged and &lt;a href="https://www.olafalders.com/2026/07/05/keeping-it-really-local/"&gt;the security settings have been improved beyond my initial suggestion&lt;/a&gt;. \o/&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.olafalders.com/2026/06/30/keep-it-local/"&gt;Read on website&lt;/a&gt;&lt;/p&gt;</content:encoded><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://www.olafalders.com/2026/06/30/keep-it-local/featured.jpeg"/></item></channel></rss>