Skip to content

mliezun/caddy-snake

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

99 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Caddy Snake 🐍

Integration Tests Linux Integration Tests Windows Go Coverage Documentation

Caddy is a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go.

Caddy Snake logo

Caddy Snake is a Caddy plugin that lets you run Python web apps directly inside Caddy — no reverse proxy needed.

It embeds Python via the C API, so your WSGI or ASGI application runs in the same process as Caddy. This means less overhead, simpler deployments, and automatic HTTPS out of the box.

To make it easier to get started you can also grab one of the precompiled binaries that comes with Caddy and Python, or one of the Docker images.

Works with Flask, Django, FastAPI, and any other WSGI/ASGI framework.


Features

  • WSGI & ASGI support — serve any Python web framework (Flask, Django, FastAPI, Starlette, etc.)
  • Multi-worker — process-based (default) or thread-based workers for concurrent request handling
  • Auto-reload — watches .py files and hot-reloads your app on changes during development
  • Dynamic module loading — use Caddy placeholders to load different apps per subdomain or route
  • Virtual environment support — point to a venv and dependencies are available automatically
  • WebSocket support — full WebSocket handling for ASGI apps
  • ASGI lifespan events — optional startup/shutdown lifecycle hooks
  • Static file serving — built-in static file support via the CLI
  • Pre-built binaries — download and run with Python embedded, no compilation required
  • Standalone app binaries — package your app + Caddy + Python into a single executable (like FrankenPHP)
  • Docker images — ready-to-use images for Python 3.12 through 3.14
  • Cross-platform — Linux, macOS, and Windows

Quick start

Option 1: Download a pre-built binary

The easiest way to get started on Linux is to download a pre-compiled Caddy binary from the latest release. It comes with Python embedded — no system Python required.

# Start a WSGI server ./caddy python-server --server-type wsgi --app main:app # Start an ASGI server ./caddy python-server --server-type asgi --app main:app

This starts a server on port 9080 serving your app. See ./caddy python-server --help for all options:

--server-type wsgi|asgi Required. Type of Python app --app <module:var> Required. Python module and app variable (e.g. main:app) --domain <example.com> Enable HTTPS with automatic certificates --listen <addr> Custom listen address (default: :9080) --workers <count> Number of worker processes (default: CPU count) --static-path <path> Serve a static files directory --static-route <route> Route prefix for static files (default: /static) --debug Enable debug logging --access-logs Enable access logs --autoreload Watch .py files and reload on changes 

Option 2: Build from source

CGO_ENABLED=1 xcaddy build --with github.com/mliezun/caddy-snake

Requirements

  • Python >= 3.12 + dev files
  • C compiler and build tools
  • Go >= 1.25 and xcaddy

Install on Ubuntu 24.04:

sudo apt-get install python3-dev build-essential pkg-config golang go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest

Option 3: Use a Docker image

Docker images are available with Python 3.12, 3.13, and 3.14:

FROM mliezun/caddy-snake:latest-py3.13 WORKDIR /app COPY . /app CMD ["caddy", "run", "--config", "/app/Caddyfile"]

Images are published to both registries:

Option 4: Package your app as a standalone binary

Package your Python app, Caddy, and Python into a single executable:

# Create app zip (Lambda-style: app + deps at root) cd myapp && pip install -r requirements.txt -t package && cd package && zip -r ../app.zip . && cd .. zip -g app.zip main.py # Build standalone binary cd caddy-snake/cmd/embed-app ./build.sh /path/to/myapp/app.zip 3.13 --output myapp ./myapp # Serves on :9080

See Apps as Standalone Binaries for the full guide.


Usage examples

Flask (WSGI)

main.py

from flask import Flask app = Flask(__name__) @app.route("/hello-world") def hello(): return "Hello world!"

Caddyfile

http://localhost:9080 {  route {  python {  module_wsgi "main:app" } } }
pip install Flask ./caddy run --config Caddyfile
curl http://localhost:9080/hello-world # Hello world!

FastAPI (ASGI)

main.py

from fastapi import FastAPI app = FastAPI() @app.get("/hello-world") def hello(): return "Hello world!"

Caddyfile

http://localhost:9080 {  route {  python {  module_asgi "main:app"  lifespan on } } }
pip install fastapi ./caddy run --config Caddyfile
curl http://localhost:9080/hello-world # Hello world!

Caddyfile reference

The python directive supports the following subdirectives:

python {  module_wsgi "module:variable"  # WSGI app to serve (e.g. "main:app")  module_asgi "module:variable"  # ASGI app to serve (e.g. "main:app")  venv "/path/to/venv"  # Virtual environment path  working_dir "/path/to/app"  # Working directory for module resolution  workers 4  # Number of worker processes (default: CPU count)  lifespan on|off  # ASGI lifespan events (default: off)  autoreload  # Watch .py files and reload on changes }

You must specify either module_wsgi or module_asgi (not both).

module_wsgi

The Python module and WSGI application variable to import, in "module:variable" format.

module_asgi

The Python module and ASGI application variable to import, in "module:variable" format.

venv

Path to a Python virtual environment. Behind the scenes, this appends venv/lib/python3.x/site-packages to sys.path so installed packages are available to your app.

Note: The venv packages are added to the global sys.path, which means all Python apps served by Caddy share the same packages.

working_dir

Sets the working directory for Python module resolution and relative paths. This is important when deploying with systemd (which defaults to /) or in monorepo/container setups where your app lives in a subdirectory.

python {  module_wsgi "main:app"  venv "/var/www/myapp/venv"  working_dir "/var/www/myapp" }

workers

Number of worker processes to spawn. Defaults to the number of CPUs (GOMAXPROCS).

lifespan

Enables ASGI lifespan events (startup and shutdown). Only applies to ASGI apps. Defaults to off.

autoreload

Watches the working directory for .py file changes and automatically reloads the Python app. Useful during development.

Changes are debounced (500ms) to handle rapid edits.

python {  module_wsgi "main:app"  autoreload }

Dynamic module loading

You can use Caddy placeholders in module_wsgi, module_asgi, working_dir, and venv to dynamically load different Python apps based on the request.

This is useful for multi-tenant setups where each subdomain or route serves a different application:

*.example.com:9080 {  route /* {  python {  module_asgi "{http.request.host.labels.2}:app"  working_dir "{http.request.host.labels.2}/" } } }

In this example, a request to app1.example.com loads the app from the app1/ directory, app2.example.com loads from app2/, and so on. Apps are lazily created on first request and cached for subsequent requests.


Hot reloading

There are two approaches for hot reloading during development:

Built-in autoreload (recommended)

Add the autoreload directive to your Caddyfile. This watches for .py file changes and reloads the app in-place without restarting Caddy:

python {  module_wsgi "main:app"  autoreload }

Using watchmedo (alternative)

You can also use watchmedo to restart Caddy on file changes:

# Install on Debian/Ubuntu sudo apt-get install python3-watchdog watchmedo auto-restart -d . -p "*.py" --recursive \ -- caddy run --config Caddyfile

Note that this restarts the entire Caddy process on changes.


Build with Docker

There's a template file in the project: builder.Dockerfile. It supports build arguments to configure which Python or Go version to use.

# Build the Docker image docker build -f builder.Dockerfile --build-arg PY_VERSION=3.13 -t caddy-snake-builder . # Extract the caddy binary to your current directory docker run --rm -v $(pwd):/output caddy-snake-builder

Make sure to match the Python version with your target environment.


Benchmarks

Caddy Snake outperforms traditional reverse proxy setups — 2.4x faster than Flask+Gunicorn and 1.6x faster than FastAPI+Uvicorn:

Benchmark Chart

Configuration Requests/sec Avg Latency (ms) P99 Latency (ms)
Flask + Gunicorn + Caddy 1,592 63.81 89.18
Flask + Caddy Snake 3,782 26.42 41.46
FastAPI + Uvicorn + Caddy 3,537 28.20 282.19
FastAPI + Caddy Snake 5,730 17.44 31.11

Benchmarked on Scaleway POP2-2C-8G (linux/amd64) with hey — 100 concurrent connections, 10s duration, 10 runs averaged, Python 3.13, Go 1.26, process workers. See benchmarks/ for methodology and how to reproduce.


Platform support

Platform Workers runtime Notes
Linux (x86_64) process, thread Primary platform, full support
Linux (arm64) process, thread Full support
macOS process, thread Full support
Windows thread only Process workers not supported on Windows

Python versions: 3.12, 3.13, 3.13-nogil (free-threaded), 3.14


LICENSE

MIT License.

Packages

 
 
 

Contributors