flareduct is a small CLI wrapper around cloudflared for common Cloudflare Tunnel workflows:
- quick, throwaway public URLs:
flareduct up 3000 - owned-domain quick tunnels:
flareduct up 3000 --subdomain demo - quick static file servers:
flareduct up .orflareduct up coolfile.html - static publishes to Cloudflare Pages:
flareduct ship .orflareduct ship coolfile.html - existing named/static Cloudflare Tunnels:
flareduct up blog
brew tap ZatTwilight/tap
brew install flareductcurl -sSL https://get.flareduct.dev | shThe default install location is ~/.local/bin. Override it with INSTALL_DIR:
curl -sSL https://get.flareduct.dev | INSTALL_DIR=/usr/local/bin shPin to a specific release:
curl -sSL https://get.flareduct.dev | VERSION=v0.1.0 shgo build -o bin/flareduct .cloudflared must be installed and available on PATH, or configured with --cloudflared / cloudflared: in the config file.
flareduct up 3000runs roughly:
cloudflared tunnel --url http://localhost:3000flareduct prints a compact panel with the public URL and writes noisy cloudflared logs to ${XDG_STATE_HOME:-~/.local/state}/flareduct/logs. Use --verbose if you want raw cloudflared logs in the terminal. Ctrl-C stops the tunnel.
You can also pass a URL or host:port:
flareduct up http://localhost:5173
flareduct up 127.0.0.1:8080Serve a directory or a single file without starting your own web server first:
flareduct up .
flareduct up coolfile.htmlFor a file target, / serves that file and sibling assets remain available by path, so a small HTML page can reference nearby CSS/images.
Static file targets run in foreground mode for now; --detach is rejected because the embedded local file server would otherwise exit immediately.
Publish a directory or single HTML file to Cloudflare Pages via wrangler:
flareduct ship .
flareduct ship ./dist --name demo
flareduct ship coolfile.htmlDirectories are uploaded as-is. Single .html / .htm files are uploaded as index.html so the generated Pages URL opens the file directly.
wrangler must be installed and authenticated first:
wrangler loginBy default, ship creates a random Pages project name like cozy-otter and publishes to the standard *.pages.dev endpoint. Use --name / --project to choose the project name.
If pages.domain or public.domain is configured and a Cloudflare API token is available, ship also attaches a matching custom domain:
pages:
# account_id is optional; flareduct auto-detects it with `wrangler whoami --json`.
account_id: 99ed1e4f3a7c251328fddfe5661a6195
domain: pages.example.comflareduct token set
flareduct ship . # https://cozy-otter.pages.example.com/ + pages.dev fallback
flareduct ship . --subdomain demo
flareduct ship . --hostname demo.example.com
flareduct ship . --pages-dev # skip custom domain attachmentThe token needs permissions for Pages custom domains plus DNS management, typically Account/Pages edit, Account read, Zone read, and DNS edit. ship creates/updates the CNAME for the custom hostname to point at <project>.pages.dev before attaching the Pages domain.
Run flareduct login once (a small wrapper around cloudflared tunnel login), then configure a domain you control:
public:
domain: dev.example.com
mode: hostname
tunnel_prefix: flareduct
random_style: words # words (default) or hex
random_words: 2Now a plain port target generates a fun word-based subdomain. flareduct explicitly creates the named tunnel, routes DNS, then runs it:
flareduct up 3000
# roughly:
# cloudflared tunnel create flareduct-cozy-otter-dev-example-com
# cloudflared tunnel route dns flareduct-cozy-otter-dev-example-com cozy-otter.dev.example.com
# cloudflared tunnel run --url http://localhost:3000 flareduct-cozy-otter-dev-example-comIf you prefer the compact hex style, set random_style: hex or pass --random-style hex; that produces names like a1b2c3.dev.example.com.
Pick the subdomain yourself:
flareduct up 3000 --subdomain demo
# https://demo.dev.example.comOr specify the full hostname:
flareduct up 3000 --hostname demo.example.comUse --trycloudflare to force the old random trycloudflare.com behavior even when public.domain is configured.
For owned quick tunnels, flareduct cleans up the Cloudflare Tunnel resource when the foreground process exits or when detached tunnels are stopped with flareduct down. Add --keep if you want to leave resources behind.
DNS record cleanup needs a Cloudflare API token because cloudflared can create DNS routes but does not expose a DNS-route delete command. Store it once:
flareduct token setThis saves the token to ~/.config/flareduct/cloudflare-api-token with 0600 permissions. CLOUDFLARE_API_TOKEN / CF_API_TOKEN still work and take priority if set.
The token needs Zone:Read and DNS:Edit for the zone. If public.domain is a subdomain, set the zone explicitly:
public:
domain: dev.example.com
zone: example.comWithout a configured token, flareduct warns and deletes the tunnel resource only; the DNS hostname may remain in Cloudflare until removed in the dashboard/API.
Cloudflare Access/auth policies are still configured in Cloudflare Zero Trust for the hostname. flareduct handles creating/running the tunnel route; Access decides who can open the URL.
flareduct up 3000 --detach
flareduct list
flareduct logs port-3000 -f
flareduct down port-3000Detached state is stored under ${XDG_STATE_HOME:-~/.local/state}/flareduct.
Useful flags:
flareduct up 3000 --detach --name demo
flareduct up api --detach --replace
flareduct up 3000 --detach --wait 30sCreate a starter config:
flareduct config initDefault path:
~/.config/flareduct/config.yamlExample:
cloudflared: cloudflared
defaults:
quick_args: []
static_args: []
public:
# Uncomment after `flareduct login` to make quick tunnels use
# owned hostnames instead of random trycloudflare.com URLs.
# domain: dev.example.com
# mode: hostname
# tunnel_prefix: flareduct
# random_style: words
# random_words: 2
# zone: example.com # optional DNS cleanup hint
services:
web:
url: http://localhost:3000
# subdomain: web
api:
port: 8080
blog:
tunnel: blog
config: ~/.cloudflared/blog.yml
public_url: https://blog.example.comThen:
flareduct up web
flareduct up api --detach
flareduct up blogIf no alias matches a non-port target, flareduct up <name> falls back to:
cloudflared tunnel run <name>so existing named tunnels can be run without adding a flareduct alias first.
flareduct up <port|url|host:port|path|alias|tunnel> [--detach] [--subdomain NAME|--hostname HOST]
flareduct ship <file|dir> [--name NAME]
flareduct list
flareduct down <name|pid>
flareduct logs <name|pid> [-f]
flareduct login
flareduct token <set|status|path|remove>
flareduct config <path|init|show>
flareduct doctor