Skip to main content

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

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)

Source distribution for caddytail 0.7.1
File Size Uploaded Provenance
caddytail-0.7.1.tar.gz 24.5 kB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for caddytail 0.7.1
File
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 log

Release 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 log

Release 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 log

Release 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 log

Release 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 log

Release 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 log

Security 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.

Select from 6 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page