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.7.0.tar.gz (24.5 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.7.0-py3-none-win_amd64.whl (22.3 MB view details)

Uploaded Python 3Windows x86-64

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

Uploaded Python 3

caddytail-0.7.0-py3-none-manylinux2014_aarch64.whl (20.1 MB view details)

Uploaded Python 3

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

Uploaded Python 3macOS 11.0+ ARM64

caddytail-0.7.0-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.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

Hashes for caddytail-0.7.0.tar.gz
Algorithm Hash digest
SHA256 b905bc8b673dc0cceb6eafd289ab2ef5277b3bb6fd38cc48d0750d0a05a9f644
MD5 57d6d6d8a39e81513c758dc7fa160894
BLAKE2b-256 2f9d83b6cd47e87884d230f15e42c351396c4af1228692df8d492b840b58b5d0

See more details on using hashes here.

Provenance

The following attestation bundles were made for caddytail-0.7.0.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.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

Hashes for caddytail-0.7.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 c971ba51fc627811a2e6b965db3485b39abd93335a4b9c86044473568a85d9bf
MD5 759871c5a647ce71977e37be4d6dba8e
BLAKE2b-256 54e9888e267dedb7660e294c72f146da17b51bc813c31af0d26eba3a0598e483

See more details on using hashes here.

Provenance

The following attestation bundles were made for caddytail-0.7.0-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.7.0-py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for caddytail-0.7.0-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 578a51158a40b222dc46efd41c98442eb972b50e4e9fe3523f9c4c2c5ab4dfd2
MD5 06602617d8130895f811e0a9da8ee2da
BLAKE2b-256 1ac4a373f356d2a30f5b82e396badcf034e8b89371dcac011fddda9f26c9c390

See more details on using hashes here.

Provenance

The following attestation bundles were made for caddytail-0.7.0-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.7.0-py3-none-manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for caddytail-0.7.0-py3-none-manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 fd1a3572ee3bd6d8e2cc4dcc30096be633632a56cb3dc385de15a21d74972262
MD5 1cdd0a01ec8f57fc84e5389e68f2b32d
BLAKE2b-256 3658f91166634e6a41797b124f8f4fd7d0455dc389f179c6177dd41f3187377e

See more details on using hashes here.

Provenance

The following attestation bundles were made for caddytail-0.7.0-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.7.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for caddytail-0.7.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 83b5ff2372b6b5ac727a70c51cfbb6264af9862d5687db15e138c5458e7c9016
MD5 8f815cc927d3efdf0264d4edc530389d
BLAKE2b-256 900412eedf32e98cf6a8c49c176a60fccfa7139ccd6d9e7c0833c348a8f50c5e

See more details on using hashes here.

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

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.7.0-py3-none-macosx_10_15_x86_64.whl.

File metadata

File hashes

Hashes for caddytail-0.7.0-py3-none-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 4a271a3f77ba4c7f7a3ad0510921569bafd94d0b4da8902a48c98c5fd2ab94ff
MD5 76fd5766e0e62d8ddb2f96847a26c2d2
BLAKE2b-256 76e554718354e8730b8b6acd4b328ecfe90f5521db6ce068ca4a0ad693e88003

See more details on using hashes here.

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

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