rs-tunnel is a Cloudflare tunnel platform with:
- CLI:
@ripeseed/rs-tunnel - API:
@ripeseed/api - Shared contracts:
@ripeseed/shared
It creates secure public hostnames for localhost services, enforces auth policy, manages Cloudflare tunnel + DNS lifecycle, and cleans stale sessions.
- Exposes local HTTP services at
https://<slug>.<base-domain> - Authenticates users through Slack OpenID
- Enforces max active tunnels per user (default:
5) - Creates/deletes DNS records with tunnel lifecycle
- Reaps stale leases when clients stop heartbeating
apps/api: Fastify API + Drizzle + Postgres + cleanup workerapps/cli: CLI (login,up,list,stop,logout,doctor)packages/shared: shared Zod schemas/contractspackages/config: shared lint/format/TypeScript config
High-level flow:
rs-tunnel login --email user@example.com- CLI starts OAuth through API
- API verifies email domain + Slack workspace and issues short-lived tokens
rs-tunnel up --port 3000 [--url my-app]- API creates Cloudflare tunnel + DNS
- CLI runs local reverse proxy +
cloudflaredand heartbeats every 20 seconds rs-tunnel stop ...removes DNS and tunnel
- Node.js 20+
- pnpm 10
- Docker (for local Postgres)
- Slack app credentials
- Cloudflare account/token with tunnel + DNS permissions
Create .env in repo root or apps/api/.env.
API_BASE_URL- Local:
http://localhost:8080 - Deployments: your public API base URL
- Local:
PORT(default:8080)DATABASE_URLJWT_SECRETREFRESH_TOKEN_SECRET
ALLOWED_EMAIL_DOMAIN(default:@example.com)ALLOWED_SLACK_TEAM_ID(required)
Compatibility fallback:
RIPSEED_SLACK_TEAM_IDis still accepted as a fallback forALLOWED_SLACK_TEAM_ID
SLACK_CLIENT_IDSLACK_CLIENT_SECRETSLACK_REDIRECT_URI(example:http://localhost:8080/v1/auth/slack/callback)
CLOUDFLARE_ACCOUNT_IDCLOUDFLARE_ZONE_IDCLOUDFLARE_API_TOKENCLOUDFLARE_BASE_DOMAIN(default:tunnel.example.com)
JWT_ACCESS_TTL_MINUTES(default:15)REFRESH_TTL_DAYS(default:30)MAX_ACTIVE_TUNNELS(default:5)HEARTBEAT_INTERVAL_SEC(default:20)LEASE_TIMEOUT_SEC(default:60)REAPER_INTERVAL_SEC(default:30)
RS_TUNNEL_API_URL(recommended global API URL override, default:http://localhost:8080)RS_TUNNEL_API_BASE_URL(legacy alias for backward compatibility)
- Install dependencies:
pnpm install- Start Postgres:
docker compose up -d postgres- Run migrations:
pnpm --filter @ripeseed/api db:migrate- Start API:
pnpm --filter @ripeseed/api dev- Run CLI against local API:
export RS_TUNNEL_API_URL=http://localhost:8080
pnpm --filter @ripeseed/rs-tunnel exec tsx src/index.ts doctor
pnpm --filter @ripeseed/rs-tunnel exec tsx src/index.ts login --email you@example.com
pnpm --filter @ripeseed/rs-tunnel exec tsx src/index.ts login --email you@example.com --skip-browser-open
pnpm --filter @ripeseed/rs-tunnel exec tsx src/index.ts up --port 3000 --url my-apprs-tunnel login --email you@example.com
rs-tunnel login --email you@example.com --skip-browser-open
rs-tunnel up --port 3000
rs-tunnel up --port 3000 --url my-app
rs-tunnel up --port 3000 --verbose
rs-tunnel list
rs-tunnel stop <tunnel-id-or-hostname>
rs-tunnel logout
rs-tunnel doctorThe CLI supports command-level --domain overrides, similar to Infisical:
rs-tunnel login --email you@example.com --domain https://api.your-company.com
rs-tunnel up --port 3000 --domain https://api.your-company.comImportant:
--domainapplies immediately to the current command and is also saved locally for future commands.- On first run (if no env/domain is configured), CLI prompts for the API domain and saves it to
~/.rs-tunnel/config.json. - For global shell-level config, set
RS_TUNNEL_API_URL. rs-tunnel login --skip-browser-openskips automatically opening the browser, prints the Slack authorize URL, and keeps waiting for the API-side auth flow to complete, which is useful when another tool needs to forward the link to the user.
rs-tunnel up renders an ngrok-style dashboard with:
- Header:
Account,Version,Region,Latency,Forwarding - Connections row:
ttl,opn,rt1,rt5,p50,p90 - Live HTTP request stream
--verbose: includes rawcloudflaredoutput in the stream
Notes:
- Region/latency are best-effort and can show
n/a - Metrics are proxy-derived approximations, not Cloudflare-native telemetry
Run from repo root:
pnpm lint
pnpm typecheck
pnpm test
pnpm buildRelease is tag-driven (v*.*.*) via .github/workflows/release.yml.
Publish order:
@ripeseed/shared@ripeseed/rs-tunnel
Registry target: npmjs (https://registry.npmjs.org).
Required GitHub Actions secret:
NPM_TOKEN(token with publish rights for the package scope)
Install example:
npm i -g @ripeseed/rs-tunnelBuild/publish shared before dependent packages.
Ensure apps/api/drizzle/meta/_journal.json exists.
Verify DATABASE_URL is loaded and complete.
Ensure SLACK_REDIRECT_URI exactly matches Slack app configuration.
apps/cli/src/lib/local-proxy.test.ts requires local socket bind permissions.
See CONTRIBUTING.md, CODE_OF_CONDUCT.md, and SECURITY.md.