The Problem
Developers exposing local services to the internet face a choice between paid tunneling services (ngrok, Cloudflare Tunnel) or complex self-hosted reverse proxy setups. pgrok attempts to bridge this by offering automatic HTTPS with on-demand TLS through a VSS, but the implementation shows significant SDLC gaps that affect production readiness.
What This Does
pgrok is a personal ngrok alternative that exposes local ports to the internet with automatic HTTPS. The architecture splits between a TypeScript TUI client and a server component running on a VPS:
- Client (
client/tui/): 13 files, 10 TypeScript modules. Built with OpenTUI, provides an interactive dashboard with request inspection, connection stats, and color-coded HTTP logs. The entry point iscreateAppinclient/tui/src/app.ts:22, which reaches 5 functions includingdestroy,Appclass, and panels for connections/requests/sessions.
- Server (
server/): 8 files. Deploys Caddy with on-demand TLS, SSH tunneling infrastructure, andpgrok-askfor certificate verification. Uses Docker Compose withnetwork_mode: hostandprivileged: true(see security concerns below).
- Tunnel flow: SSH reverse tunnels carry traffic from the VPS to the local client. When a tunnel connects, a Python script dynamically configures Caddy routes. The TUI client triggers cert provisioning during tunnel setup.
- Key functions:
stripHopByHopinclient/tui/src/proxy.tsfilters HTTP headers;spawnTunnelinclient/tui/src/tunnel.tsmanages the SSH tunnel lifecycle;recordRequestinclient/tui/src/stats.tstracks request metadata;prune(called from 2 places) cleanup stale connections.
How It Is Wired
Execution starts at createApp in client/tui/src/app.ts:22, which instantiates the App class and mounts three panels: createSessionPanel, createConnectionsPanel, and createRequestsPanel. The call graph shows createApp delegates to createHeader, createSessionPanel (which calls makeRow 4 times), and the two panel creators.
From the panels, user interactions flow through update (in both connections.ts and session.ts) which calls fmtF 4 times and fmtI 2 times for formatting. HTTP requests traverse fetch in proxy.ts, which calls stripHopByHop twice to filter hop-by-hop headers. Tunnel setup calls spawnTunnel, which invokes readStream twice for SSH stream handling. Request recording flows through recordRequest → prune (the only function called from 2 distinct call sites).
The module graph has client/tui/index as a leaf node (Ca=0, Ce=5, instability=1) and client/tui/src/app as the central hub (Ca=1, Ce=4, instability=0.8). No circular dependencies exist across 13 resolved modules, but the duplicated code blocks in connections.ts, session.ts, server/setup-vps.sh, and setup.sh (6 repeated lines across 4 files) indicate copy-paste development that should be extracted to shared helpers before production use.
How To Use It
Setup (from README and repo structure):
# Client install (requires VPS with ports 80/443 open)
curl -fsSL https://raw.githubusercontent.com/R44VC0RP/pgrok/main/install.sh | bash -s client
# Prompted for: VPS IP, domain, SSH key auto-detection
# Server setup (run on VSS)
curl -fsSL https://raw.githubusercontent.com/R44VC0RP/pgrok/main/install.sh | sudo bash -s server
# Installs Caddy, configures SSH tunneling, adds SSH key, starts services
# Build after pulling updates
./setup.sh client --rebuild
Running it:
pgrok myapp 4000 # Exposes localhost:4000 as https://myapp.yourdomain.com
pgrok api 3000 # Exposes API on https://api.yourdomain.com
pgrok staging 8080 # Any subdomain works instantly
pgrok myapp 4000 --print-logs # Debug mode dumps tunnel logs on exit
Configuration: Requires a VSS with Docker + Docker Compose, a domain you control with wildcard DNS A record pointing to the VSS IP, and an SSH key. No environment files are committed - server/.env.example exists but is not included in the repo.
Entry point: The pgrok CLI binary is installed via the install script; the TUI launches from client/tui/src/app.ts via the createApp function.
Real-World Use
A development team working on a local API server at localhost:3000 runs pgrok api 3000 on their workstation. The TUI dashboard launches showing the session provisioning Let's Encrypt cert (falling back to ZeroSSL if rate-limited), the public URL api.theirdomain.com, and real-time request stats. As team members make API calls, the dashboard logs each request with color coding: POST requests in purple, 2xx statuses in green, responses under 100ms in green. When they press Ctrl+C, the tunnel cleans up automatically and the Caddy route is removed.
Code Health & Issues
The static analysis found 8 findings (0 critical, 6 high, 2 medium, 0 low):
- [HIGH] Add a LICENSE - no licence file at repository root; default is all rights reserved, code cannot be legally reused
- [HIGH] Add a test suite - 10 source files, no test files; any change ships with no signal existing behavior holds
- [HIGH] Commit a lockfile beside
client/tui/package.json- manifest with no lockfile; unlocked ranges mean tested artifact can differ from shipped artifact - [HIGH] Add CI workflow - 10 source files, no CI configuration; every change merges with nobody having run the build
- [HIGH] Drop privileged mode and host networking -
server/docker-compose.ymlusesnetwork_mode: hostandprivileged: true, which hands the container every capability including direct device access and removes the network boundary - [HIGH] Add build gate for deployable artifacts -
server/Dockerfileuses mutable tagcaddy:latest; today's build and last month's contain different libc and a different CVE set with no record of which shipped - [MEDIUM] Enable Dependabot or Renovate - 1 manifest, no update bot configured; without a bot a published advisory sits unpatched until hand-audited
- [MEDIUM] Duplicated code blocks -
client/tui/src/ui/connections.ts,client/tui/src/ui/session.ts,server/setup-vps.sh,setup.shshare 2 repeated 6-line blocks; extract shared helpers
The Bottom Line
pgrok functionalizes a useful niche - automatic HTTPS for local development tunnels without relying on third-party services. The SSH-tunnel + Caddy approach is clever and avoids ngrok's per-session pricing. However, the repository's SDLC gaps are significant for any production adoption: no license means unclear redistribution rights, no tests or CI means regressions reach undetected, privileged Docker networking is a security liability, and the mutable Caddy base image means build reproducibility is undefined. Teams comfortable self-hosting infrastructure and willing to address the hygiene findings can benefit from this; others should consider established hosted alternatives until the repository matures.