<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Caddy Snake Blog</title>
        <link>https://caddy-snake.readthedocs.io/en/latest/blog</link>
        <description>Caddy Snake Blog</description>
        <lastBuildDate>Mon, 09 Feb 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Dynamic Modules & Autoreload: Serve Multiple Python Apps from One Caddy Instance]]></title>
            <link>https://caddy-snake.readthedocs.io/en/latest/blog/dynamic-modules-autoreload</link>
            <guid>https://caddy-snake.readthedocs.io/en/latest/blog/dynamic-modules-autoreload</guid>
            <pubDate>Mon, 09 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[This release introduces two major features: dynamic module loading and built-in autoreload. Together, they let you serve multiple Python apps from a single Caddy instance and hot-reload code changes during development — without restarting Caddy.]]></description>
            <content:encoded><![CDATA[<p>This release introduces two major features: <strong>dynamic module loading</strong> and <strong>built-in autoreload</strong>. Together, they let you serve multiple Python apps from a single Caddy instance and hot-reload code changes during development — without restarting Caddy.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="dynamic-module-loading">Dynamic Module Loading<a href="https://caddy-snake.readthedocs.io/en/latest/blog/dynamic-modules-autoreload#dynamic-module-loading" class="hash-link" aria-label="Direct link to Dynamic Module Loading" title="Direct link to Dynamic Module Loading">​</a></h2>
<p>You can now use <a href="https://caddyserver.com/docs/caddyfile/concepts#placeholders" target="_blank" rel="noopener noreferrer">Caddy placeholders</a> in <code>module_wsgi</code>, <code>module_asgi</code>, <code>working_dir</code>, and <code>venv</code> to dynamically resolve which Python app to load based on the incoming request.</p>
<p>This is perfect for multi-tenant setups where each subdomain (or route) serves a different application:</p>
<div class="language-caddyfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-caddyfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">*.example.com:9080 {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    route /* {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        python {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            module_asgi "{http.request.host.labels.2}:app"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            working_dir "{http.request.host.labels.2}/"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>With this configuration:</p>
<ul>
<li><code>app1.example.com</code> → loads <code>app1/app1.py</code></li>
<li><code>app2.example.com</code> → loads <code>app2/app2.py</code></li>
<li><code>app3.example.com</code> → loads <code>app3/app3.py</code></li>
</ul>
<p>Apps are <strong>lazily imported</strong> on first request and cached for subsequent requests. There's no limit on how many apps you can serve — each one is created on demand.</p>
<p>Under the hood, <code>DynamicApp</code> resolves placeholders at request time, builds a composite cache key, and uses double-check locking for thread-safe concurrent access. This means the fast path (cache hit) only requires a read lock, keeping things efficient even under high concurrency.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="built-in-autoreload">Built-in Autoreload<a href="https://caddy-snake.readthedocs.io/en/latest/blog/dynamic-modules-autoreload#built-in-autoreload" class="hash-link" aria-label="Direct link to Built-in Autoreload" title="Direct link to Built-in Autoreload">​</a></h2>
<p>Previously, hot-reloading required an external tool like <code>watchmedo</code> to restart the entire Caddy process on file changes. Now, Caddy-Snake has a built-in <code>autoreload</code> directive that watches for <code>.py</code> file changes and reloads your app in-place:</p>
<div class="language-caddyfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-caddyfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">python {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    module_wsgi "main:app"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    autoreload</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>When you save a Python file, the app is automatically reloaded without restarting Caddy. Here's what happens behind the scenes:</p>
<ol>
<li>A filesystem watcher (powered by <a href="https://github.com/fsnotify/fsnotify" target="_blank" rel="noopener noreferrer">fsnotify</a>) monitors your working directory recursively</li>
<li>Changes are debounced with a 500ms window to handle rapid edits (e.g. editor save + auto-format)</li>
<li>The Python module cache (<code>sys.modules</code>) is invalidated for all modules under the working directory</li>
<li>The old app is cleaned up and a new one is imported</li>
<li>A read/write lock ensures in-flight requests complete before the swap</li>
</ol>
<p>If the reload fails (e.g. syntax error), requests return HTTP 500 until the code is fixed.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="dynamic-modules--autoreload">Dynamic Modules + Autoreload<a href="https://caddy-snake.readthedocs.io/en/latest/blog/dynamic-modules-autoreload#dynamic-modules--autoreload" class="hash-link" aria-label="Direct link to Dynamic Modules + Autoreload" title="Direct link to Dynamic Modules + Autoreload">​</a></h2>
<p>The two features work together. When <code>autoreload</code> is enabled on a dynamic app, each resolved working directory gets its own filesystem watcher. Changes to <code>app1/</code> only reload <code>app1</code> — other apps remain unaffected:</p>
<div class="language-caddyfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-caddyfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">*.example.com:9080 {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    route /* {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        python {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            module_asgi "{http.request.host.labels.2}:app"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            working_dir "{http.request.host.labels.2}/"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            autoreload</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Old app instances are cleaned up after a 10-second grace period to allow in-flight requests to complete safely.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="getting-started">Getting Started<a href="https://caddy-snake.readthedocs.io/en/latest/blog/dynamic-modules-autoreload#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started">​</a></h2>
<p>Install from PyPI for the quickest setup:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">pip install caddysnake</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Or update to the <a href="https://github.com/mliezun/caddy-snake/releases" target="_blank" rel="noopener noreferrer">latest release</a> to download pre-built standalone binaries.</p>
<p>Check out the full documentation:</p>
<ul>
<li><a href="https://caddy-snake.readthedocs.io/en/latest/docs/installation">Installation &amp; Distribution</a> — all the ways to install Caddy Snake (PyPI, standalone binaries, Docker)</li>
<li><a href="https://caddy-snake.readthedocs.io/en/latest/docs/reference">Configuration Reference</a> — all directives explained in detail</li>
<li><a href="https://caddy-snake.readthedocs.io/en/latest/docs/examples">Examples</a> — working examples for dynamic modules, autoreload, and more</li>
<li><a href="https://caddy-snake.readthedocs.io/en/latest/docs/architecture">Architecture</a> — deep dive into how it all works</li>
</ul>]]></content:encoded>
            <category>Python</category>
            <category>Caddy Snake</category>
            <category>Go</category>
            <category>Web</category>
            <category>Release</category>
        </item>
        <item>
            <title><![CDATA[Python 3.14 support]]></title>
            <link>https://caddy-snake.readthedocs.io/en/latest/blog/python314</link>
            <guid>https://caddy-snake.readthedocs.io/en/latest/blog/python314</guid>
            <pubDate>Tue, 14 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Support for Python 3.14 was added in the latest release.]]></description>
            <content:encoded><![CDATA[<p>Support for Python 3.14 was added in the latest release.</p>
<p>See <a href="https://www.python.org/downloads/release/python-3140/" target="_blank" rel="noopener noreferrer">what's new</a> in Python 3.14.</p>
<p>Also check out the <a href="https://github.com/mliezun/caddy-snake/releases" target="_blank" rel="noopener noreferrer">latest release</a> of caddy-snake.</p>]]></content:encoded>
            <category>Python</category>
            <category>Support</category>
            <category>Latest</category>
            <category>3.14</category>
        </item>
        <item>
            <title><![CDATA[Caddy with an embedded python distribution]]></title>
            <link>https://caddy-snake.readthedocs.io/en/latest/blog/embed-python</link>
            <guid>https://caddy-snake.readthedocs.io/en/latest/blog/embed-python</guid>
            <pubDate>Thu, 02 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Now you can retrieve a precompiled binary of caddy with caddy-snake and your preferred version of Python.]]></description>
            <content:encoded><![CDATA[<p>Now you can retrieve a precompiled binary of caddy with caddy-snake and your preferred version of Python.</p>
<p>Check out the <a href="https://github.com/mliezun/caddy-snake/releases" target="_blank" rel="noopener noreferrer">latest release of caddy-snake</a>.</p>
<p>The following python versions are available: 3.12, 3.13 and 3.13+freethreading (nogil).</p>
<p>After downloading the precompiled binary you can do:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">$ caddy python-server --app main:app --server-type wsgi</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This will automatically start a caddy server that serves your WSGI app located in <code>main:app</code> Python module.</p>
<p>See <code>caddy python-server --help</code> for more instructions.</p>]]></content:encoded>
            <category>Python</category>
            <category>Docs</category>
            <category>Caddy Snake</category>
            <category>Go</category>
            <category>Web</category>
        </item>
        <item>
            <title><![CDATA[Docs Release]]></title>
            <link>https://caddy-snake.readthedocs.io/en/latest/blog/docs-release</link>
            <guid>https://caddy-snake.readthedocs.io/en/latest/blog/docs-release</guid>
            <pubDate>Thu, 02 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Releasing docs for caddy-snake a plugin provides native support for Python apps.]]></description>
            <content:encoded><![CDATA[<p>Releasing docs for caddy-snake a plugin provides native support for Python apps.</p>
<p>It uses the Python C API to run applications directly inside Caddy, avoiding the need for an extra layer of HTTP proxy.</p>
<p>Supports both WSGI and ASGI, which means you can run all types of frameworks like Flask, Django and FastAPI.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-caddy-snake">Why Caddy Snake?<a href="https://caddy-snake.readthedocs.io/en/latest/blog/docs-release#why-caddy-snake" class="hash-link" aria-label="Direct link to Why Caddy Snake?" title="Direct link to Why Caddy Snake?">​</a></h2>
<p>Caddy Snake simplifies the deployment of Python web applications by eliminating the need for intermediary tools like Gunicorn or Daphne. By integrating tightly with Caddy, it:</p>
<ul>
<li>
<p>Reduces complexity in your deployment stack.</p>
</li>
<li>
<p>Improves performance by cutting out unnecessary hops.</p>
</li>
<li>
<p>Offers seamless integration with Caddy's powerful features like automatic HTTPS and dynamic configuration.</p>
</li>
</ul>
<p>See how to get started in <a href="https://caddy-snake.readthedocs.io/en/latest/docs/intro">Quickstart</a>.</p>]]></content:encoded>
            <category>Python</category>
            <category>Docs</category>
            <category>Caddy Snake</category>
            <category>Go</category>
            <category>Web</category>
        </item>
    </channel>
</rss>