caddytail
Caddy web server with the Tailscale plugin, packaged for pip installation. Run any Python web app on your tailnet with one command — Flask, FastAPI, Django, or any WSGI/ASGI callable.
Installation
pip install caddytail
Quick Start
Write a normal Flask app — no CaddyTail-specific setup needed:
# app.py
from flask import Flask
from caddytail import get_user
app = Flask(__name__)
@app.get("/")
def index():
user = get_user()
return f"Hello, {user.name}!"
Or use any WSGI callable — no framework required:
# app.py
from caddytail import get_user
def app(environ, start_response):
user = get_user(environ)
body = f"Hello, {user.name}!" if user else "Not authenticated"
start_response("200 OK", [("Content-Type", "text/plain")])
return [body.encode()]
Run it on your tailnet:
caddytail run myapp app:app
That's it. Your app is now available at https://myapp.<tailnet>.ts.net with Tailscale authentication.
CLI
Hostname is always the first positional argument:
# Development — foreground, Ctrl-C kills everything
caddytail run <hostname> <app_ref> [--debug] [--env K=V]
# Production — install as systemd service + tail logs
caddytail install <hostname> <app_ref> [--no-start] [--env K=V]
# Service management
caddytail status <hostname>
caddytail logs <hostname> [-n LINES] [-f]
caddytail restart <hostname>
caddytail uninstall <hostname>
# List all installed services
caddytail list
# Pre-provision Tailscale authentication
caddytail login <hostname> [--auth-key <key>]
# Raw Caddy pass-through
caddytail caddy [args...]
The <app_ref> format is module:variable (like uvicorn), defaulting the variable to app:
app:app— importappfromapp.pymyproject.main:application— importapplicationfrommyproject/main.pyapp— shorthand forapp:app
Static File Server
A built-in WSGI file server is included. No code needed — just point it at a directory:
# Foreground
STATIC_PATH=./public caddytail run myfiles caddytail.fileserver:app
# Install as a systemd service
caddytail install myfiles caddytail.fileserver:app --env STATIC_PATH=/srv/files
STATIC_PATH defaults to . (the working directory). The server provides directory listings and serves index.html when present.
Behavior
run— starts Caddy + your app in the foreground. Ctrl-C kills everything. The framework is auto-detected: Flask and FastAPI get framework-specific middleware; generic WSGI apps are served withwsgiref; generic ASGI apps are served withuvicorn.install— writes a systemd unit file (ExecStart =caddytail run ...), enables, starts. If stdout is a tty, automatically tails logs. Ctrl-C stops tailing but leaves the service running.uninstall— stops, disables, and removes the unit file.login— authenticates a Tailscale node ahead of time. If already authenticated, returns immediately. Useful for headless provisioning with--auth-key.caddy— passes all remaining args to the bundled Caddy binary.
Python API
get_user()
Returns a TailscaleUser with .name, .login, .profile_pic:
from caddytail import get_user
# Flask — no arguments needed (uses flask.request automatically)
user = get_user()
# FastAPI / Starlette — pass the Request object
user = get_user(request)
# WSGI — pass the environ dict
user = get_user(environ)
# Django — pass request.META
user = get_user(request.META)
if user:
print(user.name) # "John Doe"
print(user.login) # "john@example.com"
print(user.profile_pic) # "https://..."
login_required
Works as both a Flask decorator and a FastAPI Depends() target:
from caddytail import login_required
# Flask
@app.get("/secret")
@login_required
def secret():
user = get_user()
return f"Hello, {user.name}!"
# FastAPI
@app.get("/secret")
async def secret(user=Depends(login_required)):
return {"message": f"Hello, {user.name}!"}
static()
Register static file paths to be served directly by Caddy:
from caddytail import static
static(app, "/assets/*", "./static")
static(app, "/uploads/*", "/var/www/uploads")
The runner picks these up automatically when starting Caddy.
CaddyTail class
For programmatic use (most users should use the CLI runner instead):
from caddytail import CaddyTail
caddy = CaddyTail(app, "myapp", debug=True)
caddy.run()
All ports are auto-allocated. No conflicts when running multiple apps.
Framework Examples
FastAPI
from fastapi import FastAPI, Request, Depends
from caddytail import get_user, login_required
app = FastAPI()
@app.get("/")
async def index(request: Request):
user = get_user(request)
return {"message": f"Hello, {user.name}!"}
@app.get("/protected")
async def protected(user=Depends(login_required)):
return {"message": f"Hello, {user.name}!"}
Django
# views.py
from django.http import HttpResponse
from caddytail import get_user
def index(request):
user = get_user(request.META)
return HttpResponse(f"Hello, {user.name}!")
Bare WSGI
from caddytail import get_user
def app(environ, start_response):
user = get_user(environ)
body = f"Hello, {user.name}!" if user else "Not authenticated"
start_response("200 OK", [("Content-Type", "text/plain")])
return [body.encode()]
ASGI
from caddytail import get_user
async def app(scope, receive, send):
# For ASGI apps, extract headers from the scope manually
...
All examples are run the same way:
caddytail run myapp myproject:app
Supported Platforms
Pre-built wheels are available for:
| Platform | Architecture |
|---|---|
| Linux (glibc) | x86_64, aarch64 |
| macOS | x86_64 (Intel), arm64 (Apple Silicon) |
| Windows | x86_64 |
Building from Source
git clone https://github.com/jpc/caddytail
cd caddytail
# Install Go and xcaddy
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
# Build caddy with the tailscale plugin
xcaddy build --with github.com/tailscale/caddy-tailscale=github.com/jpc/caddy-tailscale@main --output src/caddytail/bin/caddy
# Build the wheel
pip install build
python -m build --wheel
License
This project packages Caddy (Apache 2.0 License) with the Tailscale plugin (BSD 3-Clause License).
Links
Metadata
Release files for caddytail 0.7.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| caddytail-0.7.1.tar.gz | 24.5 kB | With provenance | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| caddytail-0.7.1-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| caddytail-0.7.1-py3-none-manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| caddytail-0.7.1-py3-none-manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| caddytail-0.7.1-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| caddytail-0.7.1-py3-none-macosx_10_15_x86_64.whl | Python 3 | none | macOS 10.15+ x86-64 | Details |
Total release size: 107.5 MB
Release files / caddytail-0.7.1.tar.gz
| Download URL | caddytail-0.7.1.tar.gz |
|---|---|
| Size | 24.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b4a74efac5e29b722ab02430271c497c88da173aafe2f787fce82028413bf823
|
|
BLAKE2b-256 checksum How to use checksums |
f9de517f4768fd7c18050042f42413a106ea60da722b93a35d8843438299eb96
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 4, 2026.
Transparency logRelease files / caddytail-0.7.1-py3-none-win_amd64.whl
| Download URL | caddytail-0.7.1-py3-none-win_amd64.whl |
|---|---|
| Size | 22.3 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
8556a9b7e25dbfa20fa18c0cc97014881cc69db9b259e784dd1b51d04fa807dd
|
|
BLAKE2b-256 checksum How to use checksums |
eafb085ec2a58a9acb12af738e7ea979cff4083df6c7bdba27ddc167e57b8f6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 4, 2026.
Transparency logRelease files / caddytail-0.7.1-py3-none-manylinux2014_x86_64.whl
| Download URL | caddytail-0.7.1-py3-none-manylinux2014_x86_64.whl |
|---|---|
| Size | 22.1 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
0a95110b8dbc2856146d1e05918232014c061a2064c927ecc48d14ae3bfc3dc7
|
|
BLAKE2b-256 checksum How to use checksums |
cb966b5f800c3b3a3a7b4920c49436cc20ec3aa00a200a53ca436695fa76898b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 4, 2026.
Transparency logRelease files / caddytail-0.7.1-py3-none-manylinux2014_aarch64.whl
| Download URL | caddytail-0.7.1-py3-none-manylinux2014_aarch64.whl |
|---|---|
| Size | 20.1 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
affd2248f782cf4da50f6461de0f46f2095acd22524c7fb00a6cc6fa9d3c254c
|
|
BLAKE2b-256 checksum How to use checksums |
a2d3295853dcdedd809764a415d41c5d7b8553cb4e4cb864e5f2cc615bd5aedc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 4, 2026.
Transparency logRelease files / caddytail-0.7.1-py3-none-macosx_11_0_arm64.whl
| Download URL | caddytail-0.7.1-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 20.7 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
17dceba17f6e380797caf74a7e60865175765faf2eeb9cb38edd644537c4c6b3
|
|
BLAKE2b-256 checksum How to use checksums |
c05638184962bdb73be62cfb7b6b16d2577f6eda19e3e8acbac23a3854de6826
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 4, 2026.
Transparency logRelease files / caddytail-0.7.1-py3-none-macosx_10_15_x86_64.whl
| Download URL | caddytail-0.7.1-py3-none-macosx_10_15_x86_64.whl |
|---|---|
| Size | 22.3 MB |
| Tags | Python 3 macOS 10.15+ x86-64 |
|
SHA-256 checksum How to use checksums |
6993db666f5ec8afc9542ccb0b53e81c774b0eec19e319eeeb0f15c159a77f3b
|
|
BLAKE2b-256 checksum How to use checksums |
992468ad19605d1f803660dc7715284fca85c4ef08e30ce5624d5d0c198a6220
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 4, 2026.
Transparency logSecurity for caddytail 0.7.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 6 files in this release have been uploaded with provenance metadata.
Attestation for caddytail-0.7.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 4, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.