@vumotions/tunnel
v1.0.3
Published
Cloudflare Named Tunnel and Quick Tunnel CLI for local development
Maintainers
Readme
@vumotions/vm-tunnel
Cloudflare Tunnel CLI for local development.
Exposes your local server to the internet with a fixed URL (Named Tunnel) or a random URL (Quick Tunnel) — zero config, reads from .env.
Features
- Named Tunnel — fixed, persistent URL via your own domain (
dev-john-api.example.com) - Quick Tunnel — zero-config random URL, no Cloudflare account needed
- Auto-fallback — if
TUNNEL_HOSTis not set, falls back to Quick Tunnel automatically - Process manager — spawn your app alongside the tunnel with
--exec - Programmatic API — use as a library in your own scripts
Installation
npm install @vumotions/vm-tunnel
# or globally
npm install -g @vumotions/vm-tunnelQuick Start
Add to your project's .env:
PORT=3000Run:
vm-tunnel
# => Quick Tunnel started at https://random-words.trycloudflare.comNamed Tunnel (fixed URL)
Requires a Cloudflare account with a domain.
Prerequisites
- Cloudflare account with a domain added — create one here
- API Token with permissions:
Cloudflare Tunnel:Edit,DNS:Edit - Account ID from your Cloudflare dashboard
- cloudflared authenticated locally:
cloudflared login # Opens browser → select your domain → cert.pem saved to ~/.cloudflared/cert.pem
Setup
Add to .env:
PORT=3000
TUNNEL_HOST=dev-john-api.example.com
CF_API_TOKEN=your_api_token
CF_ACCOUNT_ID=your_account_idRun:
vm-tunnel
# => Named Tunnel running at https://dev-john-api.example.comOn first run, vm-tunnel automatically:
- Creates the tunnel via Cloudflare API (or reuses existing one with same name)
- Configures the ingress route (
TUNNEL_HOST→http://localhost:PORT) - Creates a DNS CNAME record pointing to the tunnel
Subsequent runs reuse the existing tunnel and DNS record.
CLI Reference
Usage: vm-tunnel [options]
Options:
--host <host> Tunnel hostname (overrides TUNNEL_HOST env)
--port <port> Local app port (overrides PORT env)
--exec <command> App command to run alongside the tunnel ({TUNNEL_URL} is replaced with the actual tunnel URL)
-h, --help Display helpExamples
# Quick Tunnel on port 3000
vm-tunnel --port 3000
# Named Tunnel with inline options
vm-tunnel --host dev-john-api.example.com --port 3000
# Start app + tunnel in one command
vm-tunnel --exec "nest start --watch"
# Pass tunnel URL to your app via env or arg
vm-tunnel --exec "node server.js --public-url {TUNNEL_URL}"
# Inline everything
vm-tunnel --host dev-john-api.example.com --port 3000 --exec "nest start --watch"Typical package.json integration
{
"scripts": {
"dev": "vm-tunnel --exec \"nest start --watch\""
}
}Environment Variables
| Variable | Required | Description |
| ---------------- | -------- | ---------------------------------------------------- |
| PORT | Yes | Local port your app runs on |
| TUNNEL_HOST | No | Fixed hostname for Named Tunnel (e.g. dev-john-api.example.com). If omitted, uses Quick Tunnel |
| CF_API_TOKEN | Named | Cloudflare API token (required when TUNNEL_HOST is set) |
| CF_ACCOUNT_ID | Named | Cloudflare account ID (required when TUNNEL_HOST is set) |
Variables are loaded from
.envin the current working directory. CLI flags take precedence over env vars.
Programmatic API
import { startNamedTunnel, startQuickTunnel, loadEnv } from '@vumotions/vm-tunnel'
// Quick Tunnel
const url = await startQuickTunnel('3000')
console.log(url) // https://random-words.trycloudflare.com
// Named Tunnel
const url = await startNamedTunnel(
'dev-john-api.example.com',
'3000',
process.env.CF_API_TOKEN!,
process.env.CF_ACCOUNT_ID!
)
console.log(url) // https://dev-john-api.example.comHow It Works
Your app (localhost:PORT)
│
▼
cloudflared ──── outbound HTTPS ────▶ Cloudflare Edge
│
TUNNEL_HOST DNS CNAME
│
◀── internet traffic- Named Tunnel: creates (or reuses) a tunnel via Cloudflare API, configures ingress routes, sets a DNS CNAME, and starts
cloudflaredwith a token. The URL is always the same. - Quick Tunnel: starts
cloudflaredwith no credentials, gets a temporary random URL from Cloudflare. No account required.cloudflaredstderr is forwarded to your terminal. - All connections are outbound from your machine — no inbound ports need to be opened.
License
MIT
