Caddy web server with Tailscale plugin, packaged for pip installation
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file caddytail-0.7.0.tar.gz.
File metadata
- Download URL: caddytail-0.7.0.tar.gz
- Upload date:
- Size: 24.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b905bc8b673dc0cceb6eafd289ab2ef5277b3bb6fd38cc48d0750d0a05a9f644
|
|
| MD5 |
57d6d6d8a39e81513c758dc7fa160894
|
|
| BLAKE2b-256 |
2f9d83b6cd47e87884d230f15e42c351396c4af1228692df8d492b840b58b5d0
|
Provenance
The following attestation bundles were made for caddytail-0.7.0.tar.gz:
Publisher:
build.yml on jpc/caddytail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
caddytail-0.7.0.tar.gz -
Subject digest:
b905bc8b673dc0cceb6eafd289ab2ef5277b3bb6fd38cc48d0750d0a05a9f644 - Sigstore transparency entry: 1189184091
- Sigstore integration time:
-
Permalink:
jpc/caddytail@f003356dbeb2a6f10683710beb1e6b387f763673 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/jpc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@f003356dbeb2a6f10683710beb1e6b387f763673 -
Trigger Event:
push
-
Statement type:
File details
Details for the file caddytail-0.7.0-py3-none-win_amd64.whl.
File metadata
- Download URL: caddytail-0.7.0-py3-none-win_amd64.whl
- Upload date:
- Size: 22.3 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c971ba51fc627811a2e6b965db3485b39abd93335a4b9c86044473568a85d9bf
|
|
| MD5 |
759871c5a647ce71977e37be4d6dba8e
|
|
| BLAKE2b-256 |
54e9888e267dedb7660e294c72f146da17b51bc813c31af0d26eba3a0598e483
|
Provenance
The following attestation bundles were made for caddytail-0.7.0-py3-none-win_amd64.whl:
Publisher:
build.yml on jpc/caddytail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
caddytail-0.7.0-py3-none-win_amd64.whl -
Subject digest:
c971ba51fc627811a2e6b965db3485b39abd93335a4b9c86044473568a85d9bf - Sigstore transparency entry: 1189184119
- Sigstore integration time:
-
Permalink:
jpc/caddytail@f003356dbeb2a6f10683710beb1e6b387f763673 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/jpc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@f003356dbeb2a6f10683710beb1e6b387f763673 -
Trigger Event:
push
-
Statement type:
File details
Details for the file caddytail-0.7.0-py3-none-manylinux2014_x86_64.whl.
File metadata
- Download URL: caddytail-0.7.0-py3-none-manylinux2014_x86_64.whl
- Upload date:
- Size: 22.1 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
578a51158a40b222dc46efd41c98442eb972b50e4e9fe3523f9c4c2c5ab4dfd2
|
|
| MD5 |
06602617d8130895f811e0a9da8ee2da
|
|
| BLAKE2b-256 |
1ac4a373f356d2a30f5b82e396badcf034e8b89371dcac011fddda9f26c9c390
|
Provenance
The following attestation bundles were made for caddytail-0.7.0-py3-none-manylinux2014_x86_64.whl:
Publisher:
build.yml on jpc/caddytail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
caddytail-0.7.0-py3-none-manylinux2014_x86_64.whl -
Subject digest:
578a51158a40b222dc46efd41c98442eb972b50e4e9fe3523f9c4c2c5ab4dfd2 - Sigstore transparency entry: 1189184102
- Sigstore integration time:
-
Permalink:
jpc/caddytail@f003356dbeb2a6f10683710beb1e6b387f763673 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/jpc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@f003356dbeb2a6f10683710beb1e6b387f763673 -
Trigger Event:
push
-
Statement type:
File details
Details for the file caddytail-0.7.0-py3-none-manylinux2014_aarch64.whl.
File metadata
- Download URL: caddytail-0.7.0-py3-none-manylinux2014_aarch64.whl
- Upload date:
- Size: 20.1 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd1a3572ee3bd6d8e2cc4dcc30096be633632a56cb3dc385de15a21d74972262
|
|
| MD5 |
1cdd0a01ec8f57fc84e5389e68f2b32d
|
|
| BLAKE2b-256 |
3658f91166634e6a41797b124f8f4fd7d0455dc389f179c6177dd41f3187377e
|
Provenance
The following attestation bundles were made for caddytail-0.7.0-py3-none-manylinux2014_aarch64.whl:
Publisher:
build.yml on jpc/caddytail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
caddytail-0.7.0-py3-none-manylinux2014_aarch64.whl -
Subject digest:
fd1a3572ee3bd6d8e2cc4dcc30096be633632a56cb3dc385de15a21d74972262 - Sigstore transparency entry: 1189184114
- Sigstore integration time:
-
Permalink:
jpc/caddytail@f003356dbeb2a6f10683710beb1e6b387f763673 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/jpc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@f003356dbeb2a6f10683710beb1e6b387f763673 -
Trigger Event:
push
-
Statement type:
File details
Details for the file caddytail-0.7.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: caddytail-0.7.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 20.7 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83b5ff2372b6b5ac727a70c51cfbb6264af9862d5687db15e138c5458e7c9016
|
|
| MD5 |
8f815cc927d3efdf0264d4edc530389d
|
|
| BLAKE2b-256 |
900412eedf32e98cf6a8c49c176a60fccfa7139ccd6d9e7c0833c348a8f50c5e
|
Provenance
The following attestation bundles were made for caddytail-0.7.0-py3-none-macosx_11_0_arm64.whl:
Publisher:
build.yml on jpc/caddytail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
caddytail-0.7.0-py3-none-macosx_11_0_arm64.whl -
Subject digest:
83b5ff2372b6b5ac727a70c51cfbb6264af9862d5687db15e138c5458e7c9016 - Sigstore transparency entry: 1189184125
- Sigstore integration time:
-
Permalink:
jpc/caddytail@f003356dbeb2a6f10683710beb1e6b387f763673 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/jpc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@f003356dbeb2a6f10683710beb1e6b387f763673 -
Trigger Event:
push
-
Statement type:
File details
Details for the file caddytail-0.7.0-py3-none-macosx_10_15_x86_64.whl.
File metadata
- Download URL: caddytail-0.7.0-py3-none-macosx_10_15_x86_64.whl
- Upload date:
- Size: 22.3 MB
- Tags: Python 3, macOS 10.15+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a271a3f77ba4c7f7a3ad0510921569bafd94d0b4da8902a48c98c5fd2ab94ff
|
|
| MD5 |
76fd5766e0e62d8ddb2f96847a26c2d2
|
|
| BLAKE2b-256 |
76e554718354e8730b8b6acd4b328ecfe90f5521db6ce068ca4a0ad693e88003
|
Provenance
The following attestation bundles were made for caddytail-0.7.0-py3-none-macosx_10_15_x86_64.whl:
Publisher:
build.yml on jpc/caddytail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
caddytail-0.7.0-py3-none-macosx_10_15_x86_64.whl -
Subject digest:
4a271a3f77ba4c7f7a3ad0510921569bafd94d0b4da8902a48c98c5fd2ab94ff - Sigstore transparency entry: 1189184098
- Sigstore integration time:
-
Permalink:
jpc/caddytail@f003356dbeb2a6f10683710beb1e6b387f763673 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/jpc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@f003356dbeb2a6f10683710beb1e6b387f763673 -
Trigger Event:
push
-
Statement type: