Skip to main content

Examples

This page provides example implementations using different Python web frameworks with Caddy-Snake. Each example includes the necessary Python code, Caddy configuration, and setup instructions.


FastAPI (ASGI)

FastAPI is a modern, fast web framework for building APIs with Python.

# main.py
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None

@app.get("/")
def read_root():
return {"Hello": "World"}

@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}

@app.post("/items/")
def create_item(item: Item):
return item
# Caddyfile
http://localhost:9080 {
python /* {
module_asgi "main:app"
lifespan on
workers 1
venv "./venv"
}
}

Setup and run:

python -m venv venv
source venv/bin/activate
pip install fastapi
caddy run --config Caddyfile

Test:

curl http://localhost:9080/
curl http://localhost:9080/items/1
curl -X POST http://localhost:9080/items/ \
-H "Content-Type: application/json" \
-d '{"name":"test","price":10.5}'

Flask (WSGI)

Flask is a lightweight web framework that's great for smaller applications.

# main.py
from flask import Flask, jsonify, request

app = Flask(__name__)

@app.route("/")
def hello():
return jsonify({"message": "Hello, World!"})

@app.route("/items/<int:item_id>")
def get_item(item_id):
return jsonify({"item_id": item_id})

@app.route("/items", methods=["POST"])
def create_item():
data = request.get_json()
return jsonify(data), 201
# Caddyfile
http://localhost:9080 {
python /* {
module_wsgi "main:app"
workers 4
venv "./venv"
}
}

Setup and run:

python -m venv venv
source venv/bin/activate
pip install flask
caddy run --config Caddyfile

Test:

curl http://localhost:9080/
curl http://localhost:9080/items/1
curl -X POST http://localhost:9080/items \
-H "Content-Type: application/json" \
-d '{"name":"test"}'

Per-app environment variables

Load configuration from a dotenv file and override individual keys inline. Each python block gets its own worker environment.

route /shop/* {
python {
module_asgi "shop:app"
working_dir "/apps/shop"
env_file "/apps/shop/.env"
env_var APP_NAME "shop"
}
}

route /blog/* {
python {
module_wsgi "blog:app"
working_dir "/apps/blog"
env_file "/apps/blog/.env"
env_var APP_NAME "blog"
}
}

For multi-tenant dynamic apps, use placeholders in env_var values and place .env in each tenant directory:

*.example.com:9080 {
python /* {
module_asgi "{http.request.host.labels.2}:app"
working_dir "{http.request.host.labels.2}/"
env_file ".env"
env_var TENANT "{http.request.host.labels.2}"
}
}

Django (WSGI)

Django is a full-featured web framework with a built-in admin interface and ORM.

# mysite/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'items',
]

# items/models.py
from django.db import models

class Item(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)

# items/views.py
from django.http import JsonResponse
from .models import Item

def item_list(request):
items = Item.objects.all()
data = [{"id": item.id, "name": item.name} for item in items]
return JsonResponse(data, safe=False)
# Caddyfile
http://localhost:9080 {
python /* {
module_wsgi "mysite.wsgi:application"
workers 1
venv "./venv"
}
}

Setup and run:

python -m venv venv
source venv/bin/activate
pip install django
django-admin startproject mysite .
python manage.py migrate
caddy run --config Caddyfile

Test:

curl http://localhost:9080/items/

Django Channels (ASGI)

Django Channels extends Django with WebSocket support and other async protocols.

# mysite/asgi.py
import os
from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = get_asgi_application()
# Caddyfile
http://localhost:9080 {
python /* {
module_asgi "mysite.asgi:application"
workers 1
venv "./venv"
}
}

Setup and run:

python -m venv venv
source venv/bin/activate
pip install django channels
caddy run --config Caddyfile

Socket.IO

Socket.IO enables real-time, bidirectional communication. Here's a simple chat application:

# main.py
from fastapi import FastAPI
from socketio import AsyncServer
from socketio.asgi import ASGIApp

app = FastAPI()
sio = AsyncServer(async_mode='asgi')
socket_app = ASGIApp(sio)

@sio.event
async def connect(sid, environ):
print(f"Client connected: {sid}")

@sio.event
async def disconnect(sid):
print(f"Client disconnected: {sid}")

@sio.event
async def message(sid, data):
await sio.emit('message', data, skip_sid=sid)

app.mount('/', socket_app)
# Caddyfile
http://localhost:9080 {
python /* {
module_asgi "main:app"
lifespan on
workers 1
venv "./venv"
}
}

Setup and run:

python -m venv venv
source venv/bin/activate
pip install python-socketio fastapi
caddy run --config Caddyfile

Test with a WebSocket client:

const socket = io('http://localhost:9080');
socket.on('connect', () => {
console.log('Connected');
socket.emit('message', 'Hello, World!');
});
socket.on('message', (data) => {
console.log('Received:', data);
});

Autoreload (Development)

Enable hot-reloading during development so your app reloads automatically when you edit Python files:

# main.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
return "Hello, World!"
# Caddyfile
http://localhost:9080 {
python /* {
module_wsgi "main:app"
venv "./venv"
autoreload
}
}

Now when you edit main.py, the app reloads automatically — no need to restart Caddy. If you introduce a syntax error, requests will return HTTP 500 until you fix the code.

note

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

Alternative: watchmedo

If you prefer to restart the entire Caddy process on file changes, you can use watchmedo:

# Install on Debian/Ubuntu
sudo apt-get install python3-watchdog

watchmedo auto-restart -d . -p "*.py" --recursive \
-- caddy run --config Caddyfile

Dynamic Module Loading (Multi-Tenant)

Serve multiple Python apps from a single Caddy configuration using Caddy placeholders. Each subdomain can load a different app.

Project structure

project/
├── app1/
│ └── app1.py
├── app2/
│ └── app2.py
├── app3/
│ └── app3.py
└── Caddyfile
# app1/app1.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def index():
return {"app": "app1", "message": "Hello from App 1!"}
# app2/app2.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def index():
return {"app": "app2", "message": "Hello from App 2!"}
# Caddyfile
*.127.0.0.1.nip.io:9080 {
python /* {
module_asgi "{http.request.host.labels.6}:app"
working_dir "{http.request.host.labels.6}/"
workers 1
}
}

Run:

pip install fastapi
caddy run --config Caddyfile

Test:

curl http://app1.127.0.0.1.nip.io:9080/
# {"app":"app1","message":"Hello from App 1!"}

curl http://app2.127.0.0.1.nip.io:9080/
# {"app":"app2","message":"Hello from App 2!"}

Each app is lazily loaded on first request and cached for subsequent requests.

Dynamic modules + autoreload

Add autoreload to automatically reload individual apps when their Python files change:

*.127.0.0.1.nip.io:9080 {
python /* {
module_asgi "{http.request.host.labels.6}:app"
working_dir "{http.request.host.labels.6}/"
workers 1
autoreload
}
}

When you edit app1/app1.py, only app1 is reloaded — app2 and app3 remain unaffected.


HTTPS + on-demand TLS on nip.io (wildcard, many apps)

For real TLS on [slug].[your-public-ip].nip.io, use one site block like https://*.203.0.113.43.nip.io (swap in your VPS IPv4), enable tls { on_demand }, and gate issuance with tls.permission.python_dir so only existing /srv/apps/[slug] directories get certificates. Pair it with working_dir "/srv/apps/{http.request.host.labels.6}/" — same label index as the HTTP-only nip examples above.

See nip.io with embedded IPv4 in the Configuration Reference for the full Caddyfile pattern, ACME email caveats, and curl checks.


Notes

  • All examples assume you have Caddy-Snake installed. See Installation & Distribution for all the ways to install it — including pip install caddysnake for the quickest setup
  • The examples use different Caddy directives based on the framework:
    • module_asgi for FastAPI, Django Channels, and Socket.IO
    • module_wsgi for Flask and Django
    • lifespan on for ASGI applications that support startup/shutdown events
    • working_dir for Django to ensure proper module resolution
  • Virtual environments are recommended for all examples
  • Make sure to install all required dependencies before running the examples
  • See the Configuration Reference for full details on all directives