# https://github.com/punkpeye/pipenet Project Manual

Generated at: 2026-06-14 03:31:29 UTC

## Table of Contents

- [Overview & Getting Started](#page-1)
- [Client Architecture](#page-2)
- [Server Architecture](#page-3)
- [Deployment & Cloud Operations](#page-4)

<a id='page-1'></a>

## Overview & Getting Started

### Related Pages

Related topics: [Client Architecture](#page-2), [Server Architecture](#page-3), [Deployment & Cloud Operations](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/punkpeye/pipenet/blob/main/README.md)
- [package.json](https://github.com/punkpeye/pipenet/blob/main/package.json)
- [src/pipenet.ts](https://github.com/punkpeye/pipenet/blob/main/src/pipenet.ts)
- [src/Tunnel.ts](https://github.com/punkpeye/pipenet/blob/main/src/Tunnel.ts)
- [src/TunnelCluster.ts](https://github.com/punkpeye/pipenet/blob/main/src/TunnelCluster.ts)
- [src/server/index.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/index.ts)
- [src/server/server.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/server.ts)
- [eslint.config.ts](https://github.com/punkpeye/pipenet/blob/main/eslint.config.ts)
- [vitest.config.ts](https://github.com/punkpeye/pipenet/blob/main/vitest.config.ts)
</details>

# Overview & Getting Started

## Purpose and Scope

**pipenet** is a Node.js tunneling library and CLI that exposes a local HTTP server to the public internet by assigning it a temporary public URL. It is a modernized TypeScript fork of [localtunnel](https://github.com/localtunnel/localtunnel), maintained by Frank Fiegel and sponsored by [glama.ai](https://glama.ai) [Source: [README.md]()].

The project was created to enable local **Model Context Protocol (MCP)** servers to be reached by remote AI clients — for example, to give an AI assistant access to a local file system. This capability is now integrated into [mcp-proxy](https://github.com/punkpeye/mcp-proxy) [Source: [README.md]()].

pipenet supports the following protocols over a single tunnel [Source: [README.md]()]:

| Protocol      | Support | Notes                                  |
| ------------- | :-----: | -------------------------------------- |
| HTTP/HTTPS    |    ✅   | Standard request/response              |
| WebSocket     |    ✅   | Full duplex via HTTP upgrade           |
| SSE           |    ✅   | Server-Sent Events, long-lived HTTP    |
| HTTP Streaming|    ✅   | Chunked transfer encoding              |

## High-Level Architecture

The package ships two complementary surfaces — a **client** library that connects a local port to a remote tunnel server, and a **server** module that hosts public-facing tunnel endpoints [Source: [src/server/index.ts]()].

```mermaid
flowchart LR
  Browser[Public Client / Browser] -->|HTTPS request| Server[pipenet Server<br/>Koa + Router]
  Server -->|route by subdomain| Manager[ClientManager]
  Manager -->|match id| Agent[TunnelAgent<br/>maxConn + net.Server]
  Agent <-->|raw TCP| Client[TunnelCluster<br/>local socket]
  Client -->|forward HTTP/WS| LocalApp[Local HTTP Server]
```

The client uses [`axios`](https://www.npmjs.com/package/axios) to request a tunnel assignment from the server, then opens a long-lived TCP connection through [`TunnelCluster`](https://github.com/punkpeye/pipenet/blob/main/src/TunnelCluster.ts) [Source: [src/Tunnel.ts]()]. The server is built on [`koa`](https://github.com/koajs/koa) with [`koa-router`](https://github.com/koajs/router) for routing and [`pump`](https://github.com/mafintosh/pump) for socket streaming [Source: [package.json]()].

## Installation

pipenet is published as a native ESM package and requires **Node.js ≥ 22.0.0** [Source: [package.json]()]. The package manager pinned in the lockfile is `pnpm@10.28.0` [Source: [package.json]()].

```bash
# As a project dependency
npm install pipenet

# As a global CLI
npm install -g pipenet
```

The `bin` field exposes a `pipenet` executable that maps to the compiled CLI [Source: [package.json]()]. Type definitions ship via `dist/pipenet.d.ts`, and an additional `./server` subpath export gives access to the server module [Source: [package.json]()].

## CLI Usage

The CLI entry point offers a `client` subcommand for the most common workflow [Source: [README.md]()]:

```bash
# Expose local port 3000 to the internet
npx pipenet client --port 3000

# Request a specific subdomain
npx pipenet client --port 3000 --subdomain myapp

# Use a custom tunnel server
npx pipenet client --port 3000 --host https://your-tunnel-server.com
```

### Common Options

The CLI is built with [`yargs`](https://github.com/yargs/yargs) and supports the following options (derived from the public API and README) [Source: [README.md](), [src/pipenet.ts]()]:

| Option        | Description                                                                 |
| ------------- | --------------------------------------------------------------------------- |
| `--port`      | Local TCP port the tunnel forwards traffic to                               |
| `--subdomain` | Request a specific subdomain on the tunnel server (4–63 chars, lowercase)   |
| `--host`      | Tunnel server URL (defaults to the public pipenet.dev instance)             |
| `--local-host`| Optional explicit host for the upstream service                             |
| `--local-https` | Whether the local server speaks TLS                                      |
| `--allow-invalid-cert` | Accept self-signed upstream certificates                          |

> Community note: Issue [#1](https://github.com/punkpeye/pipenet/issues/1) reports that `pipenet client --port 3303` returned a 404 immediately after printing the public URL. This behavior was caused by the client not aborting when the server returned a 4XX response, and was fixed in **v1.4.2** by commit [e5a5a34](https://github.com/punkpeye/pipenet/commit/e5a5a3458e1ab561f06fa4a447543e4ff61db919) ("abort tunnel creation on 4XX") [Source: [src/Tunnel.ts]()].

## Programmatic API

The library exposes a top-level `pipenet` function that returns either a `Promise<Tunnel>` or, with a Node-style callback, the `Tunnel` directly [Source: [src/pipenet.ts]()]:

```ts
import { pipenet } from 'pipenet';

const tunnel = await pipenet({
  port: 3000,
  host: 'https://pipenet.dev',
});

// tunnel.url  -> "https://<random>.pipenet.dev"
// tunnel.client.close() to shut down
```

The function is overloaded to accept either `(port)` or `(options)`, plus an optional callback [Source: [src/pipenet.ts]()]. Re-exports include `Tunnel`, `TunnelCluster`, `HeaderHostTransformer`, and the option types `TunnelOptions` / `TunnelClusterOptions` / `TunnelRequest` [Source: [src/pipenet.ts]()].

The internal flow inside `Tunnel._getInfo` issues `GET ${opt.host}/${assignedDomain || '?new'}` and resolves once the server replies with a tunnel assignment. On any 4XX response, the callback now receives an error rather than silently retrying [Source: [src/Tunnel.ts]()].

## Development & Testing

The project uses [`vitest`](https://vitest.dev/) for tests, configured to discover files matching `src/**/*.spec.ts` with a 30s timeout [Source: [vitest.config.ts]()]. Linting is provided by [`eslint`](https://eslint.org/) with [`typescript-eslint`](https://typescript-eslint.io/), [`eslint-plugin-perfectionist`](https://github.com/azat-io/eslint-plugin-perfectionist) (alphabetical sort) and Prettier [Source: [eslint.config.ts]()]. The standard scripts are:

```bash
pnpm build          # tsc -> dist/
pnpm test           # vitest run
pnpm start:server   # node dist/server/index.js
```

[Source: [package.json]()]

## See Also

- [Server Module & Hooks](./server.md) — Koa routing, `ClientManager`, `TunnelAgent`, and the `--tunnel-port` cloud mode.
- [TunnelClient & TunnelCluster](./client.md) — Connection lifecycle, WebSocket upgrade, and the `HeaderHostTransformer`.
- [Issue #1 — "Gives 404"](https://github.com/punkpeye/pipenet/issues/1) — Historical failure mode now fixed in v1.4.2.

---

<a id='page-2'></a>

## Client Architecture

### Related Pages

Related topics: [Overview & Getting Started](#page-1), [Server Architecture](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [src/pipenet.ts](https://github.com/punkpeye/pipenet/blob/main/src/pipenet.ts)
- [src/Tunnel.ts](https://github.com/punkpeye/pipenet/blob/main/src/Tunnel.ts)
- [src/TunnelCluster.ts](https://github.com/punkpeye/pipenet/blob/main/src/TunnelCluster.ts)
- [src/HeaderHostTransformer.ts](https://github.com/punkpeye/pipenet/blob/main/src/HeaderHostTransformer.ts)
- [src/pipenet.spec.ts](https://github.com/punkpeye/pipenet/blob/main/src/pipenet.spec.ts)
- [package.json](https://github.com/punkpeye/pipenet/blob/main/package.json)
</details>

# Client Architecture

The pipenet **client** is the half of the system that runs inside a developer's process (or the CLI) and forwards inbound tunnel traffic to a local HTTP/WebSocket server. The whole client fits in a small set of TypeScript classes under `src/`, exposed through a single `pipenet()` factory.

## 1. High-Level Architecture

The client is built as three layered components that each own one concern:

| Layer | File | Responsibility |
| --- | --- | --- |
| Public API | `src/pipenet.ts` | Promise/callback entry point, option normalization |
| Tunnel lifecycle | `src/Tunnel.ts` | Negotiates a public URL with the server, holds tunnel state, emits events |
| TCP multiplexing | `src/TunnelCluster.ts` | Manages the long-lived TCP socket pool to the tunnel server and pipes bytes to the local server |
| Host-header rewrite | `src/HeaderHostTransformer.ts` | Rewrites `Host` headers on the way out so the local server sees its real hostname |

The default host, when none is supplied, is `https://pipenet.dev` ([src/Tunnel.ts:24-26]()). The package ships as a native ESM module with TypeScript types ([package.json:9-19]()) and depends on `axios`, `debug`, and the `ws`/Node `net` stack for the underlying transport ([package.json:50-58]()).

```mermaid
flowchart LR
    User["Caller / CLI"] -->|"pipenet(port, opts)"| API["pipenet.ts<br/>(entry)"]
    API -->|new Tunnel| T["Tunnel.ts<br/>(state + events)"]
    T -->|axios GET /?new| S[("pipenet server")]
    S -->|url, port, maxConn| T
    T -->|net.connect| C["TunnelCluster.ts<br/>(TCP pool)"]
    C -->|Host rewrite| H["HeaderHostTransformer.ts"]
    H -->|local HTTP/WS| Local[("Local server")]
    Remote["Public client"] -->|HTTPS / WSS| S
    S -->|tunneled stream| C
```

## 2. Entry Point: `pipenet()`

`src/pipenet.ts` is intentionally tiny: it is a set of TypeScript overloads that accept `(port)`, `(port, opts)`, `(opts)`, or any of those with a Node-style callback, and normalizes them into a single `TunnelOptions` object ([src/pipenet.ts:23-47]()). It then constructs a `Tunnel` and either calls `client.open(cb)` for the callback style or wraps it in a Promise for the modern async style ([src/pipenet.ts:40-47]()).

This is the surface that CLI users hit when running `npx pipenet client --port 3000`, and it is also the surface that the documented "zero-setup embedding" use case relies on: `await pipenet({ port: 3000 })` ([README.md](https://github.com/punkpeye/pipenet/blob/main/README.md)). The integration test in `src/pipenet.spec.ts` exercises exactly this shape against an in-process tunnel server, asserting that the returned `url` matches `^https?://[a-z0-9-]+\.localhost:\d+$` ([src/pipenet.spec.ts:48-58]()).

## 3. The `Tunnel` Class

`Tunnel` ([src/Tunnel.ts]()) is an `EventEmitter` that represents one live tunnel. Its public surface is:

- `cachedUrl`, `clientId`, `url` — assigned from the server's response
- `open(cb)` — kicks off the two-phase process: first `_init` negotiates a URL with the server, then `_establish` opens the cluster
- `close()` — flips `closed = true` and emits `'close'`

The `_init` step sends an HTTP GET to `${opt.host}/?new` (or `${opt.host}/${subdomain}`) with `axios` and retries every 1 s on network errors, but it now fails fast on any 4XX response ([src/Tunnel.ts:_init]()). This is the fix shipped in **v1.4.2 — "abort tunnel creation on 4XX"** ([v1.4.2 release notes]()), and it directly addresses community report **#1 "Gives 404"** ([issue #1]()), where the old code kept retrying and printing a stale-looking URL like `https://loud-shrimp-92.pipenet.dev` instead of surfacing the server's error. After a successful handshake, `_establish` creates a `TunnelCluster`, raises the listener cap to `maxConn + defaultMaxListeners`, and wires the cluster's `'open'`, `'error'`, and per-tunnel events through to the `Tunnel` ([src/Tunnel.ts:_establish]()).

## 4. `TunnelCluster`: The TCP Multiplexer

Once the server has assigned a port (random by default, or a single shared port when the server is started with `--tunnel-port` for cloud deployments — see [README.md: "Cloud Deployments"]()), `TunnelCluster` opens a `net.connect`/`tls.connect` socket to that port and demultiplexes many logical streams over it ([src/TunnelCluster.ts:25-65]()). Each incoming stream from the server is rewritten through `HeaderHostTransformer` and pipelined to `localHost:localPort` over plain HTTP or HTTPS depending on `localHttps`/`localCert`/`localKey` ([src/TunnelCluster.ts:25-50]()).

`TunnelClusterOptions` ([src/TunnelCluster.ts:14-29]()) is what the server response is mapped onto; the cluster only knows about `remoteHost`, `remotePort`, `remoteIp`, `localPort`, `localHttps`, `maxConn`, and `sharedTunnel`. The `sharedTunnel` flag is what enables single-port mode: instead of connecting to a per-tunnel random port, the client connects to the server's shared tunnel port, which is the deployment model introduced in **v1.3.0** ([v1.3.0 release notes]()). The cluster also accepts `allowInvalidCert` and a `cachedUrl` for stable reattachment across reconnects.

A bug in v1.4.0/v1.4.1 — a "close listener leak on rapid tunnel reconnections" — was fixed by [v1.4.1](https://github.com/punkpeye/pipenet/releases/tag/v1.4.1) inside this same file path; understanding `TunnelCluster`'s socket lifecycle is therefore important when debugging client reconnection behavior.

## 5. Failure Modes and Common Pitfalls

- **Server 4XX errors** (e.g. invalid subdomain, rate limit, unknown host): since v1.4.2, `Tunnel._init` rejects the promise instead of looping, so the caller sees a real error message rather than a perpetually reconnecting client ([src/Tunnel.ts:_init]()).
- **Wrong port forwarded in cloud deployments**: if the server is started without `--tunnel-port`, each client needs its own random TCP port exposed. The README's fly.io example shows the correct two-service `[[services]]` block ([README.md: "Cloud Deployments"]()).
- **Process exits immediately after the URL is printed**: this matches the symptom in [issue #1](https://github.com/punkpeye/pipenet/issues/1). The tunnel is held open by the long-lived `TunnelCluster` socket; the process only stays alive while that socket and any open streams are active, so an embedding script must `await` the `Tunnel` or attach an event listener before letting the event loop drain.
- **Reusing a stale URL**: the client supports `cachedUrl` so a process restart can try to reattach to the same tunnel, but the server must still have that tunnel registered.

## See Also

- [Server Architecture](./Server-Architecture) — the receiving side of the same protocol
- [Tunnel Lifecycle and Events](./Tunnel-Lifecycle) — event semantics for `Tunnel` and `TunnelCluster`
- [Cloud Deployment Guide](./Cloud-Deployment) — `--tunnel-port` and single-port mode
- [Project README](https://github.com/punkpeye/pipenet/blob/main/README.md)

---

<a id='page-3'></a>

## Server Architecture

### Related Pages

Related topics: [Client Architecture](#page-2), [Deployment & Cloud Operations](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [src/server/server.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/server.ts)
- [src/server/ClientManager.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/ClientManager.ts)
- [src/server/Client.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/Client.ts)
- [src/server/TunnelAgent.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/TunnelAgent.ts)
- [src/server/TunnelServer.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/TunnelServer.ts)
- [src/server/index.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/index.ts)
- [src/server/server.spec.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/server.spec.ts)
- [package.json](https://github.com/punkpeye/pipenet/blob/main/package.json)
</details>

# Server Architecture

## Overview

The pipenet **server** is the public-facing counterpart to the pipenet client. It accepts inbound HTTP, HTTPS, WebSocket, SSE, and chunked-streaming traffic on one or more public domains and proxies each request back through a long-lived TCP connection to the originating tunnel client, which in turn forwards it to a local server. The server is implemented as a single factory, `createServer`, that wires together a Koa HTTP front-end with a TCP tunnel layer and a `ClientManager` that tracks live tunnels. Source: [src/server/server.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/server.ts)

The public server entry point exports the factory and its associated types from `src/server/index.ts`, allowing the same code to be embedded by the hosted `pipenet.dev` service, by `npx pipenet server`, or by any program that needs a self-hosted tunnel endpoint. Source: [src/server/index.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/index.ts)

The runtime is Node.js (>=22) and uses `koa`, `koa-router`, `axios`, `debug`, `pump`, `tldjs`, `human-readable-ids`, and `yargs`. Source: [package.json](https://github.com/punkpeye/pipenet/blob/main/package.json)

## Core Components

The server is composed of five cooperating classes. The first three are always active; the last two are mode-dependent.

| Component | File | Responsibility |
| --- | --- | --- |
| `createServer` / `PipenetServer` | `src/server/server.ts` | Builds the Koa app, registers routes, validates subdomains, surfaces hooks |
| `ClientManager` | `src/server/ClientManager.ts` | Owns the live `Client` map, assigns IDs, exposes aggregate stats |
| `Client` | `src/server/Client.ts` | Wraps one tunnel; parses inbound HTTP, handles WebSocket upgrades, pumps bytes |
| `TunnelAgent` | `src/server/TunnelAgent.ts` | Per-client TCP listener that caps `maxTcpSockets` and meters traffic |
| `TunnelServer` | `src/server/TunnelServer.ts` | Single-port TCP multiplexer for cloud deployments (since v1.3.0) |

### `createServer` and `ServerOptions`

`createServer(options?)` returns a `PipenetServer` (a Koa `http.Server`-compatible object). `ServerOptions` accepts `address`, `domains`, `maxTcpSockets`, a `secure` flag, and a `hooks` object. The factory installs request routing, the `/api/status` and `/api/tunnels/:id/status` endpoints, the root redirect to `https://pipenet.dev/`, and a WebSocket upgrader. Source: [src/server/server.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/server.ts)

Subdomain names must be lowercase alphanumeric and between 4 and 63 characters; oversized or malformed requests are rejected with a descriptive error. Source: [src/server/server.spec.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/server.spec.ts)

### `ClientManager`

`ClientManager` is the registry of active tunnels. It tracks a `Map<id, Client>`, assigns random human-readable IDs via the `human-readable-ids` package when a preferred subdomain is unavailable, and exposes `stats` and `hasClient` for inspection. Source: [src/server/ClientManager.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/ClientManager.ts)

### `Client` and `TunnelAgent`

Each `Client` owns a `TunnelAgent` that listens on a random local TCP port. Inbound HTTP traffic to the public URL is reparsed, re-serialized, and pumped through the agent socket to the pipenet client, which then replays it against the user's local server. WebSocket upgrades are detected on the HTTP request and proxied bidirectionally. Source: [src/server/Client.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/Client.ts)

`TunnelAgent` enforces `maxTcpSockets`, supports `createConnection()` for both legacy and `sharedTunnel` modes, and exposes `stats()` for monitoring. Source: [src/server/TunnelAgent.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/TunnelAgent.ts)

### `TunnelServer` (shared-port mode)

When `TunnelServer` is enabled, all clients connect to a single TCP port and identify themselves with an ID as the first message. The server routes each accepted socket to the registered handler. This is what makes pipenet deployable to platforms like fly.io, Docker, or Kubernetes where only a few ports can be exposed. Source: [src/server/TunnelServer.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/TunnelServer.ts)

## Request Flow

The diagram below traces a single inbound request through the architecture, from public internet to local server and back.

```mermaid
flowchart LR
  A[Public client] -->|HTTPS to loud-shrimp-92.pipenet.dev| B(Koa router)
  B --> C{Subdomain in ClientManager?}
  C -- no --> Z[404 / error]
  C -- yes --> D[Client]
  D --> E[TunnelAgent]
  E -->|TCP to tunnel port| F[Remote pipenet client]
  F --> G[Local server]
  G --> F --> E --> D --> B --> A
  H[TunnelServer / shared port] -. multiplexes .-> F
```

In shared-port mode, step 3b is replaced by a single `TunnelServer` that demultiplexes incoming connections by client ID before handing the socket to the correct `Client` handler. Source: [src/server/TunnelServer.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/TunnelServer.ts)

## Configuration, Hooks, and Common Failures

**Multiple domains.** `ServerOptions.domains` accepts an array; each public host the server should serve must be listed, otherwise requests are rejected. Multiple-domain support was added in v1.2.0. Source: [src/server/server.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/server.ts)

**Hooks.** Added in v1.4.0, the optional `ServerHooks` interface lets operators observe lifecycle events such as `onRequest` (with `domain`, `path`, `tunnelId`, `headers`, `remoteAddress`) for logging, rate limiting, or authentication. Source: [src/server/server.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/server.ts)

**Cloud deployment.** The CLI flag `--tunnel-port <port>` activates the shared `TunnelServer`. A typical fly.io configuration exposes one HTTP port (80/443) and the dedicated tunnel port (e.g. 8081); both must be declared in `fly.toml`. Source: [src/server/TunnelServer.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/TunnelServer.ts)

**API endpoints.** `GET /api/status` reports server-wide tunnel counts; `GET /api/tunnels/:id/status` reports a specific tunnel. The root `/` redirects to the project landing page. Source: [src/server/server.spec.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/server.spec.ts)

**4XX errors and tunnel creation.** v1.4.2 introduced a fix that aborts tunnel creation whenever the server returns a 4XX response (e.g. invalid subdomain, server policy). This addresses the "Gives 404" community report where `npx pipenet client --port 3303` produced a URL but every request to it returned 404 — the client should now fail fast instead of handing out a non-functional URL. Source: [src/Tunnel.ts](https://github.com/punkpeye/pipenet/blob/main/src/Tunnel.ts)

**Listener leaks.** v1.4.1 fixed a leak of close listeners on rapid tunnel reconnections, so a server that experiences flapping clients no longer accumulates listeners on its `net.Server`. Source: [src/server/TunnelServer.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/TunnelServer.ts)

## See Also

- [README.md](https://github.com/punkpeye/pipenet/blob/main/README.md) — usage examples and comparison with localtunnel/zrok
- [src/pipenet.ts](https://github.com/punkpeye/pipenet/blob/main/src/pipenet.ts) — the client-side factory used by `npx pipenet client`
- [src/TunnelCluster.ts](https://github.com/punkpeye/pipenet/blob/main/src/TunnelCluster.ts) — client-side TCP cluster that pairs with `TunnelAgent`

---

<a id='page-4'></a>

## Deployment & Cloud Operations

### Related Pages

Related topics: [Overview & Getting Started](#page-1), [Server Architecture](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [src/cli.ts](https://github.com/punkpeye/pipenet/blob/main/src/cli.ts)
- [src/server/server.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/server.ts)
- [src/server/TunnelServer.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/TunnelServer.ts)
- [src/server/TunnelAgent.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/TunnelAgent.ts)
- [src/server/ClientManager.ts](https://github.com/punkpeye/pipenet/blob/main/src/server/ClientManager.ts)
- [src/Tunnel.ts](https://github.com/punkpeye/pipenet/blob/main/src/Tunnel.ts)
- [src/TunnelCluster.ts](https://github.com/punkpeye/pipenet/blob/main/src/TunnelCluster.ts)
- [src/pipenet.ts](https://github.com/punkpeye/pipenet/blob/main/src/pipenet.ts)
- [package.json](https://github.com/punkpeye/pipenet/blob/main/package.json)
- [README.md](https://github.com/punkpeye/pipenet/blob/main/README.md)
</details>

# Deployment & Cloud Operations

## Overview

pipenet is an HTTP/WebSocket tunneling service that exposes a local server to the public internet. In production it is split into two cooperating processes: a **public tunnel server** that accepts inbound HTTP traffic on a hostname, and one or more **tunnel clients** that establish an outbound TCP connection back to that server and proxy requests to a local port.

The "Deployment & Cloud Operations" surface of the project covers everything required to run the server in a containerized or hosted environment: starting the Koa-based server, configuring domains, enabling the single-port shared-tunnel mode required for environments that only expose a fixed port set (Docker, fly.io, Kubernetes), observing runtime state, and integrating with the `pipenet` programmatic API. Source: [README.md]() documents this split and the key operational difference from the upstream `localtunnel` project: pipenet's `--tunnel-port` flag.

The package exposes a `bin` entry, a programmatic `createServer` factory, and a client API. Source: [package.json]() defines `"main": "./dist/pipenet.js"`, the `bin.pipenet` script (`./dist/cli.js`), and an additional sub-export at `./server` so consumers can import server types without pulling in the client. The Node engine is pinned at `>=22.0.0`, which must be respected when selecting container base images.

## Server Lifecycle and Configuration

The server is created with `createServer(opt)` from [src/server/server.ts](), which constructs a Koa application, a router, an optional `TunnelServer`, and a `ClientManager`. CLI flags in [src/cli.ts]() map directly onto the `ServerOptions` interface. The most relevant deployment options are:

| CLI flag | Option | Purpose |
| --- | --- | --- |
| `--port` | `port` | Public HTTP listener port |
| `--secure` | `secure` | Serve over HTTPS instead of HTTP |
| `--domain` | `domain[]` | One or more base domains for subdomains (added in v1.2.0) |
| `--tunnel-port` | `tunnelPort` | Activate shared-tunnel mode for cloud deployments (added in v1.3.0) |
| `--landing` | `landing` | URL returned by `GET /` redirects |
| `--max-sockets` | `maxSockets` | Per-client socket cap |
| `--address` | `address` | Bind address (added in v1.3.0) |

When `tunnelPort` is supplied, `createServer` instantiates a `TunnelServer` and passes it to the `ClientManager`. Source: [src/server/server.ts]() shows this conditional construction: `const tunnelServer = opt.tunnelPort ? new TunnelServer() : undefined;` followed by `new ClientManager({ ...opt, tunnelServer })`. Without `tunnelPort`, each tunnel client asks the server to allocate a fresh random TCP port — this works on a developer machine but is incompatible with most hosted platforms that only expose a fixed set of inbound ports.

## Shared Tunnel Mode for Cloud Deployments

The shared-tunnel architecture is the primary cloud-deployment story. In this mode every client connects to a single known TCP port on the tunnel server. The server demultiplexes traffic by client id, which is safer for containerized platforms because it removes the requirement to publish a wide port range. Source: [src/server/TunnelServer.ts]() implements the demultiplexer, while [src/server/TunnelAgent.ts]() tracks per-client state, including `started`, `maxTcpSockets`, and live `TunnelAgentStats`.

A typical cloud deployment therefore looks like:

```mermaid
flowchart LR
    Internet((Internet clients)) -->|HTTPS on --port| Server[pipenet server]
    Server -->|Koarouter| ClientMgr[ClientManager]
    ClientMgr -->|demux by client id| TunnelSrv[TunnelServer on --tunnel-port]
    TunnelSrv -->|TCP socket pool| Agent1[TunnelAgent client A]
    TunnelSrv -->|TCP socket pool| Agent2[TunnelAgent client B]
    Agent1 --> Local1[Local service on host A]
    Agent2 --> Local2[Local service on host B]
```

Source: [README.md]() frames the operational distinction this way: "localtunnel creates a random TCP port for each tunnel client, which doesn't work in containerized environments like Docker, fly.io, or Kubernetes where only specific ports are exposed. pipenet solves this with the `--tunnel-port` option, enabling all clients to connect through a single shared port."

The shared-port design pairs with the client side. Source: [src/TunnelCluster.ts]() describes `TunnelClusterOptions.sharedTunnel`, which is set when the server advertises single-port mode. The cluster opens a single TCP connection to the server and routes each HTTP request through it. Source: [src/Tunnel.ts]() composes one or more `TunnelCluster` instances and exposes reconnect, header-preservation, and `localHttps` support.

## CLI Commands and Operational Patterns

The CLI entry point in [src/cli.ts]() dispatches on the first positional argument:

```bash
# Run a public tunnel server in cloud mode
npx pipenet server --port 3000 --tunnel-port 3000

# Connect a client through a hosted server
npx pipenet client --port 3000 --host https://tunnel.example.com

# Request a specific subdomain
npx pipenet client --port 3000 --subdomain myapp
```

`runClient` parses optional headers as JSON, instantiates the client, and prints the assigned `tunnel.url`. The `runServer` branch (defined in the same file) wires `createServer` to `opt.port` and `opt.address`, and delegates HTTP listener lifecycle to Koa. Source: [src/cli.ts]() shows the `ClientOptions` and `ServerOptions` TypeScript interfaces; the CLI is the canonical example for deploying without writing custom code.

For programmatic deployment — typical when embedding pipenet inside another service such as `mcp-proxy` — the public API in [src/pipenet.ts]() exposes the `pipenet()` factory, which accepts either `(port, options)` or a single options object and returns a `Promise<Tunnel>` (or, when a callback is supplied, returns the `Tunnel` synchronously for backwards compatibility).

## Observability, Hooks, and Common Failures

Operators typically want runtime visibility. Source: [src/server/server.ts]() exposes `GET /api/status`, which returns `{ mem: process.memoryUsage(), tunnels: manager.stats.tunnels }`, and `GET /api/tunnels` for active tunnel listings. The `ClientManager` exposes aggregated `stats.tunnels` and per-client `TunnelAgentStats`, so a sidecar healthcheck can poll `/api/status` and alert when the tunnel count drops unexpectedly. Hooks for request/error lifecycle events were added in v1.4.0 (commit `e96df31`) so operators can plug in logging or metrics without forking the server.

A known operational failure mode reported by users is the "your url is: ... pipenet.dev then returns the prompt" behavior in [issue #1](https://github.com/punkpeye/pipenet/issues/1). The root cause is that the default `--host` is not always resolvable in some network environments, so the public hostname is assigned but the local server cannot be reached. v1.4.2 added a fix (commit `e5a5a34`) to abort tunnel creation on 4XX, which surfaces the failure rather than silently returning a dead URL. v1.4.1 (commit `95ad0be`) also closed a socket listener leak that occurred on rapid reconnects — relevant for ops scripts that restart clients on a tight loop.

Operational best practices distilled from the source:

- **Pin Node** to `>=22.0.0` per [package.json]() engine field.
- **Use `--tunnel-port`** in any containerized deployment to avoid port-range publishing, per [src/server/server.ts]() and [README.md]().
- **List every served domain** with repeated `--domain` flags; v1.2.0 made multi-domain explicit. Source: [src/server/server.ts]() shows `validHosts` flowing into `tldjs.fromUserSettings`.
- **Monitor `/api/status`** for `tunnels` count and RSS; drop in custom hooks (v1.4.0) to ship logs.
- **Upgrade to ≥ v1.4.2** to ensure 4XX responses abort tunnel creation instead of returning an invalid URL.

## See Also

- [README.md]() — high-level overview, comparison with localtunnel and zrok
- [src/pipenet.ts]() — programmatic client API
- [src/server/server.ts]() — `createServer` factory
- [src/server/TunnelServer.ts]() — shared-port demultiplexer
- [src/server/TunnelAgent.ts]() — per-client socket pool
- [src/cli.ts]() — CLI surface and option definitions

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: punkpeye/pipenet

Summary: Found 7 structured pitfall item(s), including 0 high/blocking item(s). Top priority: Installation risk - Installation risk requires verification.

## 1. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/punkpeye/pipenet/issues/1

## 2. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | https://github.com/punkpeye/pipenet

## 3. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/punkpeye/pipenet

## 4. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | https://github.com/punkpeye/pipenet

## 5. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | https://github.com/punkpeye/pipenet

## 6. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/punkpeye/pipenet

## 7. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/punkpeye/pipenet

<!-- canonical_name: punkpeye/pipenet; human_manual_source: deepwiki_human_wiki -->
