Skip to main content

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 — import app from app.py
  • myproject.main:application — import application from myproject/main.py
  • app — shorthand for app: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 with wsgiref; generic ASGI apps are served with uvicorn.
  • 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

caddytail-0.6.1.tar.gz (25.0 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

caddytail-0.6.1-py3-none-win_amd64.whl (22.3 MB view details)

Uploaded Python 3Windows x86-64

caddytail-0.6.1-py3-none-manylinux2014_x86_64.whl (22.1 MB view details)

Uploaded Python 3

caddytail-0.6.1-py3-none-manylinux2014_aarch64.whl (20.0 MB view details)

Uploaded Python 3

caddytail-0.6.1-py3-none-macosx_11_0_arm64.whl (20.7 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

caddytail-0.6.1-py3-none-macosx_10_15_x86_64.whl (22.3 MB view details)

Uploaded Python 3macOS 10.15+ x86-64

File details

Details for the file caddytail-0.6.1.tar.gz.

File metadata

  • Download URL: caddytail-0.6.1.tar.gz
  • Upload date:
  • Size: 25.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for caddytail-0.6.1.tar.gz
Algorithm Hash digest
SHA256 c3a10c991895eb3bae89739205f202f0f590764d5efaed6baa3178e4b456de94
MD5 a6bfedd9b496c103df27fffc051ac26e
BLAKE2b-256 4f9cd6a81e92f6af11ba6543ebf20561e374cdf6e9b4521fcce33e5910d49d96

See more details on using hashes here.

Provenance

The following attestation bundles were made for caddytail-0.6.1.tar.gz:

Publisher: build.yml on jpc/caddytail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file caddytail-0.6.1-py3-none-win_amd64.whl.

File metadata

  • Download URL: caddytail-0.6.1-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

Hashes for caddytail-0.6.1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 6f7370b1ceaf512b3c770021f1a95075190b8938913a267052d87d99f167ec73
MD5 02693079f3cb8b1e536a835c709a3f1f
BLAKE2b-256 f5229f0fecec24c2dc479d35724c04d4818c430cf515d8f7ec1669c2cbbbcf5b

See more details on using hashes here.

Provenance

The following attestation bundles were made for caddytail-0.6.1-py3-none-win_amd64.whl:

Publisher: build.yml on jpc/caddytail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file caddytail-0.6.1-py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for caddytail-0.6.1-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d52697235376662f6a0f1a8bc58e9c21866aa4b631077764e24925a8d129ce55
MD5 bd6f928e5903743536abbe080fd32f8b
BLAKE2b-256 364579268edd7db881c46d38e3f194cd1a8f5dfbdb496b8af9a02b784f8beede

See more details on using hashes here.

Provenance

The following attestation bundles were made for caddytail-0.6.1-py3-none-manylinux2014_x86_64.whl:

Publisher: build.yml on jpc/caddytail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file caddytail-0.6.1-py3-none-manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for caddytail-0.6.1-py3-none-manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 6041d7c81b5bc4b4ef445d53056cbd43d97462dd99bde9317094197b4e0fef88
MD5 67a31184a036040daddc54d23c8faac5
BLAKE2b-256 e65d029713b7e0cd03ec4e8719f1c59b92b2e56e5e540d99765e6f5a25845041

See more details on using hashes here.

Provenance

The following attestation bundles were made for caddytail-0.6.1-py3-none-manylinux2014_aarch64.whl:

Publisher: build.yml on jpc/caddytail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file caddytail-0.6.1-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for caddytail-0.6.1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 44dea35e6f880dc60e79493c72bfe0c2cfc797e28ae4892ff1f4f89dda067d26
MD5 2c2afa3c158edeb6ce004dd57ed48d0d
BLAKE2b-256 c39428245c1b320b99503565f05e92170edadaa6a29f76ec8ac44e968a5a710f

See more details on using hashes here.

Provenance

The following attestation bundles were made for caddytail-0.6.1-py3-none-macosx_11_0_arm64.whl:

Publisher: build.yml on jpc/caddytail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file caddytail-0.6.1-py3-none-macosx_10_15_x86_64.whl.

File metadata

File hashes

Hashes for caddytail-0.6.1-py3-none-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 29b8de8f2d67876072e0900105115d17300da017c5c8815a8f6b28419f0e2403
MD5 f914a073cd2a183639cfb3bfff9da1e4
BLAKE2b-256 3cd49725c5ef1f29129086a49887a625807b8da37cb75027d7b10c6c2feef877

See more details on using hashes here.

Provenance

The following attestation bundles were made for caddytail-0.6.1-py3-none-macosx_10_15_x86_64.whl:

Publisher: build.yml on jpc/caddytail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page