{"id":34039732,"url":"https://github.com/mliezun/caddy-snake","last_synced_at":"2026-05-04T13:05:03.205Z","repository":{"id":222932580,"uuid":"758683503","full_name":"mliezun/caddy-snake","owner":"mliezun","description":"Caddy plugin to serve Python apps","archived":false,"fork":false,"pushed_at":"2026-03-16T17:07:11.000Z","size":1799,"stargazers_count":159,"open_issues_count":5,"forks_count":5,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-03-17T04:29:23.879Z","etag":null,"topics":["asgi","caddy","caddy-plugin","go","http2","http3","https","python","wsgi"],"latest_commit_sha":null,"homepage":"https://caddy-snake.readthedocs.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mliezun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2024-02-16T20:55:04.000Z","updated_at":"2026-03-16T17:06:24.000Z","dependencies_parsed_at":"2024-02-22T22:28:40.107Z","dependency_job_id":"aecbbc9f-6eb4-4b83-8722-194b676a2b97","html_url":"https://github.com/mliezun/caddy-snake","commit_stats":null,"previous_names":["mliezun/caddy-snake"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/mliezun/caddy-snake","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mliezun%2Fcaddy-snake","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mliezun%2Fcaddy-snake/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mliezun%2Fcaddy-snake/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mliezun%2Fcaddy-snake/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mliezun","download_url":"https://codeload.github.com/mliezun/caddy-snake/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mliezun%2Fcaddy-snake/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31294527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T01:43:37.129Z","status":"online","status_checked_at":"2026-04-02T02:00:08.535Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asgi","caddy","caddy-plugin","go","http2","http3","https","python","wsgi"],"created_at":"2025-12-13T21:47:10.148Z","updated_at":"2026-05-04T13:05:03.196Z","avatar_url":"https://github.com/mliezun.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Caddy Snake 🐍\n\n[![Integration Tests Linux](https://github.com/mliezun/caddy-snake/actions/workflows/integration-tests-linux.yml/badge.svg)](https://github.com/mliezun/caddy-snake/actions/workflows/integration-tests-linux.yml)\n[![Integration Tests Windows](https://github.com/mliezun/caddy-snake/actions/workflows/integration-tests-windows.yml/badge.svg)](https://github.com/mliezun/caddy-snake/actions/workflows/integration-tests-windows.yml)\n[![Go Coverage](https://github.com/mliezun/caddy-snake/wiki/coverage.svg)](https://raw.githack.com/wiki/mliezun/caddy-snake/coverage.html)\n[![Documentation](https://img.shields.io/badge/docs-latest-blue)](https://caddy-snake.readthedocs.io/en/latest/)\n\n\u003e [Caddy](https://github.com/caddyserver/caddy) is a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go.\n\n[![Caddy Snake logo](docs/static/img/caddysnake-512x512.png)](https://caddy-snake.readthedocs.io/en/latest/docs/intro)\n\nCaddy Snake is a Caddy plugin that lets you **run Python web apps directly inside Caddy** — no reverse proxy needed.\n\nIt 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.\n\nTo make it easier to get started you can also grab one of the precompiled binaries that come with Caddy and Python, or one of the Docker images.\n\n**Works with Flask, Django, FastAPI, and any other WSGI/ASGI framework.**\n\n---\n\n## Features\n\n- **WSGI \u0026 ASGI support** — serve any Python web framework (Flask, Django, FastAPI, Starlette, etc.)\n- **Multi-worker** — process-based (default) or thread-based workers for concurrent request handling\n- **Auto-reload** — watches `.py` files and hot-reloads your app on changes during development\n- **Dynamic module loading** — use Caddy placeholders to load different apps per subdomain or route\n- **On-demand TLS permission (`tls.permission.python_dir`)** — gate HTTPS issuance with filesystem checks so wildcard-style hosts work without running a separate ACME [`ask`](https://caddyserver.com/docs/caddyfile/options#on-demand-tls) service (pairs with dynamic `working_dir`)\n- **Virtual environment support** — point to a `venv` and dependencies are available automatically\n- **WebSocket support** — full WebSocket handling for ASGI apps\n- **ASGI lifespan events** — optional startup/shutdown lifecycle hooks\n- **Static file serving** — built-in static file support via the CLI\n- **Pre-built binaries** — download and run with Python embedded, no compilation required\n- **Standalone app binaries** — package your app + Caddy + Python into a single executable (like FrankenPHP)\n- **Docker images** — ready-to-use images for Python 3.12 through 3.14\n- **Cross-platform** — Linux, macOS, and Windows\n\n---\n\n## Quick start\n\n### Option 1: Download a pre-built binary\n\nThe easiest way to get started on Linux is to download a pre-compiled Caddy binary from the [latest release](https://github.com/mliezun/caddy-snake/releases). It comes with Python embedded — no system Python required.\n\n```bash\n# Start a WSGI server\n./caddy python-server --server-type wsgi --app main:app\n\n# Start an ASGI server\n./caddy python-server --server-type asgi --app main:app\n```\n\nThis starts a server on port `9080` serving your app. See `./caddy python-server --help` for all options:\n\n```\n--server-type wsgi|asgi   Required. Type of Python app\n--app \u003cmodule:var\u003e        Required. Python module and app variable (e.g. main:app)\n--domain \u003cexample.com\u003e    Enable HTTPS with automatic certificates\n--listen \u003caddr\u003e           Custom listen address (default: :9080)\n--workers \u003ccount\u003e         Number of worker processes (default: CPU count)\n--python-path \u003cpath\u003e      Path to Python executable (default: embedded Python)\n--working-dir \u003cpath\u003e      Working directory for the Python app\n--venv \u003cpath\u003e             Path to virtual environment\n--static-path \u003cpath\u003e      Serve a static files directory\n--static-route \u003croute\u003e    Route prefix for static files (default: /static)\n--debug                   Enable debug logging\n--access-logs             Enable access logs\n--lifespan on|off         Enable ASGI lifespan events (default: off)\n--autoreload              Watch .py files and reload on changes\n```\n\n### Option 2: Build from source\n\n```bash\nCGO_ENABLED=1 xcaddy build --with github.com/mliezun/caddy-snake\n```\n\n#### Requirements\n\n- Python \u003e= 3.12 + dev files\n- C compiler and build tools\n- Go \u003e= 1.26 and [xcaddy](https://github.com/caddyserver/xcaddy)\n\nInstall on Ubuntu 24.04:\n\n```bash\nsudo apt-get install python3-dev build-essential pkg-config golang\ngo install github.com/caddyserver/xcaddy/cmd/xcaddy@latest\n```\n\n### Option 3: Use a Docker image\n\nDocker images are available with Python 3.12, 3.13, and 3.14:\n\n```Dockerfile\nFROM mliezun/caddy-snake:latest-py3.13\n\nWORKDIR /app\nCOPY . /app\n\nCMD [\"caddy\", \"run\", \"--config\", \"/app/Caddyfile\"]\n```\n\nImages are published to both registries:\n\n- [Docker Hub](https://hub.docker.com/r/mliezun/caddy-snake)\n- [GitHub Container Registry](https://github.com/mliezun/caddy-snake/pkgs/container/caddy-snake)\n\n### Option 4: Package your app as a standalone binary\n\nPackage your Python app, Caddy, and Python into a single executable:\n\n```bash\n# Create app zip (Lambda-style: app + deps at root)\ncd myapp \u0026\u0026 pip install -r requirements.txt -t package \u0026\u0026 cd package \u0026\u0026 zip -r ../app.zip . \u0026\u0026 cd ..\nzip -g app.zip main.py\n\n# Build standalone binary\ncd caddy-snake/cmd/embed-app\n./build.sh /path/to/myapp/app.zip 3.13 --output myapp\n\n./myapp  # Serves on :9080\n```\n\nSee [Apps as Standalone Binaries](https://caddy-snake.readthedocs.io/en/latest/docs/embed-app/) for the full guide.\n\n---\n\n## Usage examples\n\n### Flask (WSGI)\n\n`main.py`\n\n```python\nfrom flask import Flask\n\napp = Flask(__name__)\n\n@app.route(\"/hello-world\")\ndef hello():\n    return \"Hello world!\"\n```\n\n`Caddyfile`\n\n```Caddyfile\nhttp://localhost:9080 {\n    route {\n        python {\n            module_wsgi \"main:app\"\n        }\n    }\n}\n```\n\n```bash\npip install Flask\n./caddy run --config Caddyfile\n```\n\n```bash\ncurl http://localhost:9080/hello-world\n# Hello world!\n```\n\n### FastAPI (ASGI)\n\n`main.py`\n\n```python\nfrom fastapi import FastAPI\n\napp = FastAPI()\n\n@app.get(\"/hello-world\")\ndef hello():\n    return \"Hello world!\"\n```\n\n`Caddyfile`\n\n```Caddyfile\nhttp://localhost:9080 {\n    route {\n        python {\n            module_asgi \"main:app\"\n            lifespan on\n        }\n    }\n}\n```\n\n```bash\npip install fastapi\n./caddy run --config Caddyfile\n```\n\n```bash\ncurl http://localhost:9080/hello-world\n# Hello world!\n```\n\n---\n\n## Caddyfile reference\n\nThe `python` directive supports the following subdirectives:\n\n```Caddyfile\npython {\n    module_wsgi \"module:variable\"       # WSGI app to serve (e.g. \"main:app\")\n    module_asgi \"module:variable\"       # ASGI app to serve (e.g. \"main:app\")\n    venv \"/path/to/venv\"                # Virtual environment path\n    working_dir \"/path/to/app\"          # Working directory for module resolution\n    workers 4                           # Number of worker processes (default: CPU count)\n    lifespan on|off                     # ASGI lifespan events (default: off)\n    autoreload                          # Watch .py files and reload on changes\n}\n```\n\nYou must specify either `module_wsgi` or `module_asgi` (not both).\n\n### `module_wsgi`\n\nThe Python module and WSGI application variable to import, in `\"module:variable\"` format.\n\n### `module_asgi`\n\nThe Python module and ASGI application variable to import, in `\"module:variable\"` format.\n\n### `venv`\n\nPath 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.\n\n\u003e **Note:** The venv packages are added to the global `sys.path`, which means all Python apps served by Caddy share the same packages.\n\n### `working_dir`\n\nSets 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.\n\n```Caddyfile\npython {\n    module_wsgi \"main:app\"\n    venv \"/var/www/myapp/venv\"\n    working_dir \"/var/www/myapp\"\n}\n```\n\n### `workers`\n\nNumber of worker processes to spawn. Defaults to the number of CPUs (`GOMAXPROCS`).\n\n### `lifespan`\n\nEnables ASGI [lifespan events](https://asgi.readthedocs.io/en/latest/specs/lifespan.html) (`startup` and `shutdown`). Only applies to ASGI apps. Defaults to `off`.\n\n### `autoreload`\n\nWatches the working directory for `.py` file changes and automatically reloads the Python app. Useful during development.\n\nChanges are debounced (500ms) to handle rapid edits.\n\n```Caddyfile\npython {\n    module_wsgi \"main:app\"\n    autoreload\n}\n```\n\n---\n\n## Dynamic module loading\n\nYou can use [Caddy placeholders](https://caddyserver.com/docs/caddyfile/concepts#placeholders) in `module_wsgi`, `module_asgi`, `working_dir`, and `venv` to dynamically load different Python apps based on the request.\n\nThis is useful for multi-tenant setups where each subdomain or route serves a different application:\n\n```Caddyfile\n*.example.com:9080 {\n    route /* {\n        python {\n            module_asgi \"{http.request.host.labels.2}:app\"\n            working_dir \"{http.request.host.labels.2}/\"\n        }\n    }\n}\n```\n\nIn 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.\n\n---\n\n## On-demand TLS without `ask`\n\n[Caddy on-demand TLS](https://caddyserver.com/docs/caddyfile/options#on-demand-tls) issues certificates for hostnames as clients connect. That flow normally requires an **`ask`** HTTP endpoint — unless you plug in a **`tls.permission.*`** module.\n\nCaddy Snake ships **`tls.permission.python_dir`**, which allows issuance only when the hostname matches **`{slug}.{domain_suffix}`** (exactly one label before the suffix) and **`{root}/{slug}`** exists as a directory — the same layout you use for dynamic Python apps.\n\nMinimal pattern (wildcard HTTPS site + dynamic `working_dir`; adjust **`domain_suffix`** and placeholder index to match your host shape):\n\n```Caddyfile\n{\n    email you@yourcompany.com\n\n    on_demand_tls {\n        permission python_dir {\n            root /srv/apps\n            domain_suffix project.example\n        }\n    }\n}\n\nhttps://*.project.example {\n    tls {\n        on_demand\n    }\n\n    route /* {\n        python {\n            module_asgi \"{http.request.host.labels.2}:app\"\n            working_dir \"/srv/apps/{http.request.host.labels.2}/\"\n        }\n    }\n}\n```\n\nUse a working mailbox on a **registrable domain** for `email` (Let's Encrypt rejects contacts at `example.com` and similar). For nip.io-style hosts and WSGI-only setups, see the **[configuration reference](https://caddy-snake.readthedocs.io/en/latest/docs/reference/)** and **[examples](https://caddy-snake.readthedocs.io/en/latest/docs/examples/)**.\n\nFor **[nip.io](https://nip.io/)** names like **`app7.203.0.113.43.nip.io`**, the slug sits at **`{http.request.host.labels.6}`** (seven labels total).\n\n---\n\n## Hot reloading\n\nThere are two approaches for hot reloading during development:\n\n### Built-in autoreload (recommended)\n\nAdd the `autoreload` directive to your Caddyfile. This watches for `.py` file changes and reloads the app in-place without restarting Caddy:\n\n```Caddyfile\npython {\n    module_wsgi \"main:app\"\n    autoreload\n}\n```\n\n### Using watchmedo (alternative)\n\nYou can also use [watchmedo](https://github.com/gorakhargosh/watchdog?tab=readme-ov-file#shell-utilities) to restart Caddy on file changes:\n\n```bash\n# Install on Debian/Ubuntu\nsudo apt-get install python3-watchdog\n\nwatchmedo auto-restart -d . -p \"*.py\" --recursive \\\n    -- caddy run --config Caddyfile\n```\n\nNote that this restarts the entire Caddy process on changes.\n\n---\n\n## Build with Docker\n\nThere's a template file in the project: [builder.Dockerfile](/builder.Dockerfile). It supports build arguments to configure which Python or Go version to use.\n\n```bash\n# Build the Docker image\ndocker build -f builder.Dockerfile --build-arg PY_VERSION=3.13 -t caddy-snake-builder .\n\n# Extract the caddy binary to your current directory\ndocker run --rm -v $(pwd):/output caddy-snake-builder\n```\n\nMake sure to match the Python version with your target environment.\n\n---\n\n## Benchmarks\n\nCaddy Snake outperforms traditional reverse proxy setups — **2.4x faster** than Flask+Gunicorn and **1.6x faster** than FastAPI+Uvicorn:\n\n![Benchmark Chart](benchmarks/benchmark_chart.svg)\n\n| Configuration | Requests/sec | Avg Latency (ms) | P99 Latency (ms) |\n|---|---|---|---|\n| Flask + Gunicorn + Caddy | 1,592 | 63.81 | 89.18 |\n| **Flask + Caddy Snake** | **3,782** | **26.42** | **41.46** |\n| FastAPI + Uvicorn + Caddy | 3,537 | 28.20 | 282.19 |\n| **FastAPI + Caddy Snake** | **5,730** | **17.44** | **31.11** |\n\n\u003e Benchmarked on Scaleway POP2-2C-8G (linux/amd64) with [hey](https://github.com/rakyll/hey) — 100 concurrent connections, 10s duration, 10 runs averaged, Python 3.13, Go 1.26, process workers. See [benchmarks/](benchmarks/) for methodology and how to reproduce.\n\n---\n\n## Platform support\n\n| Platform       | Workers runtime   | Notes                                    |\n|----------------|-------------------|------------------------------------------|\n| Linux (x86_64) | process, thread   | Primary platform, full support           |\n| Linux (arm64)  | process, thread   | Full support                             |\n| macOS          | process, thread   | Full support                             |\n| Windows        | thread only       | Process workers not supported on Windows |\n\n**Python versions:** 3.12, 3.13, 3.13-nogil (free-threaded), 3.14\n\n---\n\n## LICENSE\n\n[MIT License](/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmliezun%2Fcaddy-snake","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmliezun%2Fcaddy-snake","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmliezun%2Fcaddy-snake/lists"}