diff --git a/README.md b/README.md index e01f2fc..8798e62 100644 --- a/README.md +++ b/README.md @@ -14,34 +14,95 @@ kportal runs many Kubernetes port-forwards from one YAML file and shows their st ![kportal Screenshot](docs/kportal-screenshot.png) +Screenshot from kportal v0.2.17. The current key bar also lists `PgUp`/`PgDn`, `b` (Bench) and `l` (Logs). + Website: +## Contents + +- [Features](#features) +- [Comparison](#comparison-with-other-tools) +- [Installation](#installation) +- [Quick start](#quick-start) +- [Configuration](#configuration) +- [Usage](#usage) +- [Status indicators](#status-indicators) +- [Advanced features](#advanced-features) +- [Migration from kftray](#migration-from-kftray) +- [Signal handling](#signal-handling) +- [Troubleshooting](#troubleshooting) +- [Development](#development) +- [Telemetry](#telemetry) +- [License](#license) + ## Features | Area | What kportal does | |------|-------------------| | Forwards | Forwards to `service/`, `pod/` (exact name or name prefix), or `pod` with a label `selector`. Only TCP is supported. | | Terminal UI | Add (`n`), edit (`e`), delete (`d`) and toggle (`Space`) forwards without a restart. The add wizard writes the config file. | -| Table columns | `tui.columns` sets which columns the forwards table shows, their order and their width. It applies to the UI and to the `-verbose` table. | +| Table columns | `tui.columns` sets which columns the forwards table shows, their order and their width. It applies to the UI and to the `-v` table. | | Add wizard | If the cluster does not allow listing namespaces, the wizard asks you to type the namespace. | | Reconnect | Retries with exponential backoff: 1s, 2s, 4s, 8s, then 10s, with 10% jitter. It does not stop retrying. | | Pod restarts | Forwards by pod prefix or selector connect to a new pod after a restart. | | Health checks | `tcp-dial` or `data-transfer` check every 3s by default. A connection is stale when it is older than `maxConnectionAge` and idle, or idle longer than `maxIdleTime`. | | Hot reload | The file watcher and `SIGHUP` reload the config. An invalid config is rejected and the previous one stays active. | -| Contexts | Several Kubernetes contexts and namespaces in one file. | +| Contexts | Several Kubernetes contexts and namespaces in one file. `--context` selects which contexts run. | | Port conflicts | Startup and reload checks report a busy local port and the process that holds it. | | mDNS | With `mdns.enabled: true`, each forward is reachable as `.local`. Without an alias, the resource name is used. | -| HTTP log | Per-forward request and response log in the UI, with detail view, filter, search, JSON formatting, gzip/deflate decoding and clipboard copy. Header values such as `Authorization` and `Cookie` are redacted. | +| HTTP log | Per-forward request and response log in the UI, with detail view, filter, search, JSON formatting, gzip/deflate decoding and clipboard copy. | +| Header redaction | With `includeHeaders: true`, values of sensitive headers such as `Authorization` and `Cookie` become `[REDACTED]`. You cannot turn this off. | | Benchmark | Press `b` to send HTTP requests through a forward. Reports success and failure counts, min/max/avg latency, P50/P95/P99, requests per second and status codes. | | Generate | `kportal generate` lists services in a cluster and appends the ones you pick to the config with consecutive local ports. | | Headless | `-headless` runs without the UI and logs to stderr. `-log-format json` switches the log format. | -| Import | `--convert` turns a kftray JSON file into a kportal YAML file. | +| Import | `-convert` turns a kftray JSON file into a kportal YAML file. | | Shell completion | `kportal completion` prints a bash, zsh or fish completion script, or installs it. | +| Verified installer | `install.sh` checks the archive SHA-256 and, if `cosign` is on `PATH`, the keyless signature of the checksums file. | | Updates | `-update` checks for a newer release. `-version` prints the version. | -## Install +## Comparison with other tools -Homebrew (macOS): +Compared on 2026-10-02 against each project's README and docs. Check their repositories for changes. The kportal column comes from this repository's code. "Not documented" means the project's README and docs do not say. + +| Feature | kportal | kubectl port-forward | k9s | kubefwd | kftray | +|---------|---------|----------------------|-----|---------|--------| +| Many forwards in one config file | Yes | No | Partial (annotations configure forwards; no forward list file) | Partial (`example.fwdconf.yml` exists) | Yes | +| Automatic reconnect | Yes | Not documented | Not documented | Yes | Yes | +| Follows pod restarts by label or name prefix | Yes | Not documented | Not documented | Yes (label filter) | Yes | +| Terminal UI of all forwards | Yes | No | Yes (PortForward view, alias `pf`; forwards last for the session) | Yes | Yes | +| GUI | No | No | No | No | Yes | +| Config hot reload | Yes (file watcher, `SIGHUP`) | No | Partial (`benchmarks.yaml` only) | Not documented | Not documented | +| HTTP request and response logging | Yes | No | Not documented | Not documented | Yes | +| Health checks | Yes (`tcp-dial`, `data-transfer`) | Not documented | Not documented | Not documented | Partial (README says it tracks pod health) | +| mDNS or friendly hostnames | Yes (mDNS) | No | No | Partial (edits `/etc/hosts`, not mDNS; needs root) | Not documented | +| Built-in benchmark | Yes | No | Yes (`hey`, key `b`) | Not documented | Not documented | +| Imports another tool's config | Yes (kftray JSON) | No | No | Not documented | Partial (JSON, GitHub repos, service annotations; not other tools' formats) | +| Several contexts in one config | Yes | No | Partial | Partial | Not documented | +| Local port conflict detection | Yes (reports the holding process) | Partial (`:remotePort` picks a free port) | Not documented | Yes | Not documented | +| Platforms | Linux, macOS, Windows | Not documented | macOS, Linux, Windows | macOS, Linux, Windows | macOS, Linux, Windows | +| Licence | MIT | Not documented | Apache-2.0 | Apache-2.0 | GPL-3.0 | +| Language | Go | Not documented | Go | Go | Rust (Tauri, Ratatui) | +| Latest release (2026-10-02) | [Releases](https://github.com/lukaszraczylo/kportal/releases) | Not documented | v0.51.0 (2026-06-06) | v1.25.16 (2026-06-20) | v0.29.2 (2026-09-29) | + +When another tool fits better: + +- kubectl port-forward for a one-off forward. It needs no config file and ships with kubectl. +- k9s for general cluster browsing and its built-in benchmark. +- kubefwd to forward every service in a namespace with local DNS names. +- kftray for a tray GUI, shared JSON configs and UDP. + +Kube Forwarder is a GUI-only forwarder. Its last release was 2019-08-14. + +Sources: + +- kubectl: [Use Port Forwarding to Access Applications in a Cluster](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) +- k9s: and +- kubefwd: +- kftray: + +## Installation + +### Homebrew (macOS) ```bash brew install --cask lukaszraczylo/taps/kportal @@ -49,7 +110,7 @@ brew install --cask lukaszraczylo/taps/kportal If you installed the formula (`brew install lukaszraczylo/taps/kportal`) earlier, run `brew uninstall kportal` first. -Install script: +### Quick install ```bash curl -fsSL https://raw.githubusercontent.com/lukaszraczylo/kportal/main/install.sh | bash @@ -61,10 +122,23 @@ The script downloads `kportal--checksums.txt` from the same release and |----------|--------| | `DRY_RUN=1` | Download and verify only. Do not install. | | `SKIP_COSIGN=1` | Skip the cosign check. The SHA-256 check still runs. | +| `INSTALL_DIR` | Target directory. Default `/usr/local/bin`. | +| `KPORTAL_VERSION` | Install a specific version, for example `1.2.3`, instead of the latest. | +| `COSIGN_CERT_IDENTITY_REGEXP` | Certificate identity to accept. Set it if you fork the release pipeline. | -Manual download: the [releases page](https://github.com/lukaszraczylo/kportal/releases) has archives for Linux, macOS and Windows on amd64 and arm64. +Example: -Build from source (Go 1.26 or later, see `go.mod`): +```bash +curl -fsSL https://raw.githubusercontent.com/lukaszraczylo/kportal/main/install.sh | DRY_RUN=1 bash +``` + +### Manual download + +The [releases page](https://github.com/lukaszraczylo/kportal/releases) has archives for Linux, macOS and Windows on amd64 and arm64. Archives are named `kportal---.tar.gz`, and `.zip` on Windows. Each release also has a `kportal--checksums.txt` file. + +### Build from source + +Go 1.26 or later is required (see `go.mod`). ```bash git clone https://github.com/lukaszraczylo/kportal.git @@ -72,10 +146,13 @@ cd kportal make build && make install ``` -### Verify a release signature +`make install` copies the binary to `/opt/homebrew/bin` if that directory exists, otherwise to `~/.local/bin` on macOS and Linux. + +### Verifying release signatures + +Release checksums are signed with [cosign](https://github.com/sigstore/cosign) using keyless signing. Download the checksums file and its sigstore bundle from the release, then run: ```bash -# Download the checksums file and its sigstore bundle from the release cosign verify-blob \ --certificate-identity-regexp "^https://github\.com/lukaszraczylo/shared-actions/\.github/workflows/go-release\.yaml@refs/heads/main$" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ @@ -83,6 +160,8 @@ cosign verify-blob \ kportal--checksums.txt ``` +Replace `` with the release version, for example `1.2.3`. Then check the archive against the verified checksums file with `shasum -a 256` or `sha256sum`. + ## Quick start Create `.kportal.yaml`: @@ -107,13 +186,149 @@ contexts: httpLog: true ``` +Each `name` under `contexts` must exist in your kubeconfig. List them with `kubectl config get-contexts`. + Run: ```bash kportal ``` -## CLI +### Keyboard controls + +Forwards list: + +| Key | Action | +|-----|--------| +| `Up` `Down` / `j` `k` | Move the selection | +| `PgUp` `PgDn` / `Ctrl+u` `Ctrl+d` | Move by 10 rows | +| `Space` / `Enter` | Toggle the forward | +| `n` | Add a forward | +| `e` | Edit the forward | +| `d` | Delete the forward (asks to confirm) | +| `b` | Benchmark the forward | +| `l` | Open the HTTP log | +| `q` / `Ctrl+c` | Quit | + +In the add and edit wizard, `h` on the confirmation step toggles `httpLog`. Advanced `httpLog` keys you set in YAML stay unchanged. + +HTTP log keys are listed under [HTTP traffic logging](#http-traffic-logging). + +## Configuration + +### Basic structure + +```yaml +contexts: + - name: + namespaces: + - name: + forwards: + - resource: / + protocol: tcp + port: + localPort: + alias: # optional + selector: # optional + httpLog: true # optional +``` + +### Forward options + +| Field | Required | Description | +|-------|----------|-------------| +| `resource` | Yes | `service/`, `pod/`, or `pod` with `selector` | +| `protocol` | No | `tcp`. No other value is accepted. | +| `port` | Yes | Remote port | +| `localPort` | Yes | Local port | +| `alias` | No | Display name and mDNS hostname | +| `selector` | With bare `pod` | Label selector. Not allowed with `pod/`. | +| `httpLog` | No | `true`, or a map of options (see [HTTP traffic logging](#http-traffic-logging)) | + +### Resource formats + +| Format | Description | +|--------|-------------| +| `service/name` | Forward to a service | +| `pod/name` | Forward to a pod by exact name, or by name prefix | +| `pod` with `selector` | Forward to a pod picked by label selector | + +Valid resource types are `pod` and `service`. To reach a deployment, use `pod` with a `selector`. + +### Health checks and reliability + +```yaml +healthCheck: + interval: "3s" + timeout: "2s" + method: "data-transfer" # or tcp-dial + maxConnectionAge: "25m" + maxIdleTime: "10m" + +reliability: + tcpKeepalive: "30s" + dialTimeout: "30s" + retryOnStale: true + watchdogPeriod: "30s" +``` + +The values above are the defaults from `.kportal.yaml`. + +- `tcp-dial` opens a TCP connection to the local port. +- `data-transfer` also tries to read data through the tunnel. It is the default. +- A connection past `maxConnectionAge` reconnects only when it is also idle, so a running transfer is not cut. +- `watchdogPeriod` is the interval of the check for hung workers. + +### mDNS + +```yaml +mdns: + enabled: true + +contexts: + - name: production + namespaces: + - name: default + forwards: + - resource: service/postgres + port: 5432 + localPort: 5432 + alias: prod-db # reachable as prod-db.local:5432 +``` + +- `alias: prod-db` publishes `prod-db.local`. +- Without an alias, `service/redis` publishes `redis.local`. +- A bare `pod` with a selector and no alias is not published. +- Services register as `_kportal._tcp`. + +Check registration: + +```bash +dns-sd -B _kportal._tcp local # macOS +avahi-browse -t _kportal._tcp # Linux +``` + +### Table columns + +```yaml +tui: + columns: + - name: alias + width: 30 # optional, 1-200 + - name: resource + - name: local + - name: status +``` + +- Valid names: `context`, `namespace`, `alias`, `type`, `resource`, `remote`, `local`, `status`. +- List order is render order. Unlisted columns are hidden. +- `width` is the maximum text width, 1 to 200. Without it, the column keeps its default width. +- Without `tui`, the table keeps its default columns and order. +- The setting applies to the UI and to the `-v` table, and reloads with the file. + +## Usage + +### Command line | Command or flag | Effect | |-----------------|--------| @@ -122,32 +337,52 @@ kportal | `-v` | Verbose logging. In headless mode it sets the log level only. | | `-headless` | No UI. Logs go to stderr. | | `-check` | Validate the config and exit. | -| `-context ` | Forward only the named contexts. See [Select contexts](#select-contexts). | +| `-context ` | Forward only the named contexts. Repeatable and comma-separated. | | `-log-format text\|json` | Log format. Default `text`. | | `-convert ` | Convert a kftray JSON file to kportal YAML. | | `-convert-output ` | Output of `-convert`. Default `.kportal.yaml`. | | `-update` | Check for updates and exit. | | `-version` | Print the version and exit. | -| `kportal generate` | Add forwards from a cluster. See [Generate](#generate-forwards-from-a-cluster). | -| `kportal completion` | Print a shell completion script. | +| `kportal generate` | Add forwards from a cluster. | +| `kportal completion` | Print or install a shell completion script. | Each flag also accepts the double-dash form, for example `--check`. -Headless example: +### Interactive mode ```bash +kportal +``` + +### Verbose mode + +```bash +kportal -v +``` + +Without `-headless`, `-v` prints a plain-text table that redraws. The table uses the same status words as the UI. When stdout is not a terminal, it draws `✓ Active`, `⋯ Starting`, `↻ Reconnecting` and `✗ Error`. + +### Headless mode + +```bash +kportal -headless kportal -headless -v 2>kportal.log & +kportal -headless -log-format json ``` -Completion: +Headless mode logs to stderr, so you can redirect it to a file or the systemd journal. `-v` sets the log level, not the destination. + +### Validate configuration ```bash -kportal completion --shell zsh # print the script -kportal completion --shell zsh --install # install it -kportal completion --uninstall # remove it +kportal -check ``` -Without `--shell`, kportal detects the shell. +### Custom config file + +```bash +kportal -c /path/to/config.yaml +``` ### Select contexts @@ -185,7 +420,7 @@ contexts: Without `--context`, or with both contexts named, the duplicate port is an error. -### Generate forwards from a cluster +### Generate ```bash kportal generate --context=my-cluster @@ -207,25 +442,25 @@ Steps: Press `enter` on the last step to save (or print, with `--dry-run`), `b` to go back, `esc` to cancel. -## Keys +The `--dry-run` output has this form (example values): -Forwards list: +```text +[dry-run] Would add 2 forwards to .kportal.yaml + 10000 → my-cluster/default/service/api:8080 + 10001 → my-cluster/default/service/postgres:5432 +``` -| Key | Action | -|-----|--------| -| `Up` `Down` / `j` `k` | Move the selection | -| `PgUp` `PgDn` / `Ctrl+u` `Ctrl+d` | Move by 10 rows | -| `Space` / `Enter` | Toggle the forward | -| `n` | Add a forward | -| `e` | Edit the forward | -| `d` | Delete the forward (asks to confirm) | -| `b` | Benchmark the forward | -| `l` | Open the HTTP log | -| `q` / `Ctrl+c` | Quit | +### Shell completion -In the add and edit wizard, `h` on the confirmation step toggles `httpLog`. Advanced `httpLog` keys you set in YAML stay unchanged. +```bash +kportal completion --shell zsh # print the script +kportal completion --shell zsh --install # install it +kportal completion --uninstall # remove it +``` -Status values: +Without `--shell`, kportal detects the shell. Supported shells: bash, zsh, fish. + +## Status indicators | Indicator | Meaning | |-----------|---------| @@ -235,95 +470,9 @@ Status values: | `✗ Error` | Connection failed | | `○ Disabled` | Turned off in the UI | -## Configuration +## Advanced features -### Forwards - -```yaml -contexts: - - name: - namespaces: - - name: - forwards: - - resource: / - protocol: tcp - port: - localPort: - alias: # optional - selector: # optional - httpLog: true # optional -``` - -| Field | Required | Description | -|-------|----------|-------------| -| `resource` | Yes | `service/`, `pod/`, or `pod` with `selector` | -| `protocol` | No | `tcp`. No other value is accepted. | -| `port` | Yes | Remote port | -| `localPort` | Yes | Local port | -| `alias` | No | Display name and mDNS hostname | -| `selector` | With bare `pod` | Label selector. Not allowed with `pod/`. | -| `httpLog` | No | `true`, or a map of options (see [HTTP log](#http-log)) | - -### Health checks and reliability - -```yaml -healthCheck: - interval: "3s" - timeout: "2s" - method: "data-transfer" # or tcp-dial - maxConnectionAge: "25m" - maxIdleTime: "10m" - -reliability: - tcpKeepalive: "30s" - dialTimeout: "30s" - retryOnStale: true - watchdogPeriod: "30s" -``` - -The values above are the defaults from `.kportal.yaml`. - -- `tcp-dial` opens a TCP connection to the local port. -- `data-transfer` also tries to read data through the tunnel. It is the default. -- A connection past `maxConnectionAge` reconnects only when it is also idle, so a running transfer is not cut. - -### mDNS - -```yaml -mdns: - enabled: true -``` - -- `alias: prod-db` publishes `prod-db.local`. -- Without an alias, `service/redis` publishes `redis.local`. -- A bare `pod` with a selector and no alias is not published. - -Check registration: - -```bash -dns-sd -B _kportal._tcp local # macOS -avahi-browse -t _kportal._tcp # Linux -``` - -### Table columns - -```yaml -tui: - columns: - - name: alias - width: 30 # optional, 1-200 - - name: resource - - name: local - - name: status -``` - -- Valid names: `context`, `namespace`, `alias`, `type`, `resource`, `remote`, `local`, `status`. -- List order is render order. Unlisted columns are hidden. -- `width` is the maximum text width, 1 to 200. Without it, the column keeps its default width. -- Without `tui`, the table keeps its default columns and order. -- The setting applies to the UI and to the `-verbose` table, and reloads with the file. - -### HTTP log +### HTTP traffic logging Press `l` on a forward that has `httpLog` set. @@ -335,11 +484,13 @@ forwards: httpLog: enabled: true includeHeaders: true # sensitive values are redacted - maxBodySize: 65536 # bytes, 0 = unlimited + maxBodySize: 65536 # bytes filterPath: "/api/" # log only paths containing this text logFile: "api.log" # also append entries to this file ``` +`maxBodySize` defaults to 1 MB (1048576 bytes) when you omit it or set a value of 0 or less. + List view columns: TIME, METHOD, STATUS, LATENCY, PATH. | Key | Action | @@ -353,22 +504,49 @@ List view columns: TIME, METHOD, STATUS, LATENCY, PATH. | `c` | Clear filters | | `q` | Close | -Detail view shows sorted request and response headers, bodies and timing. JSON bodies are pretty-printed, gzip and deflate bodies are decoded, and binary bodies show a placeholder. +The detail view shows sorted request and response headers, bodies and timing. JSON bodies are pretty-printed, gzip and deflate bodies are decoded, and binary bodies show a placeholder. | Key | Action | |-----|--------| | `Up` `Down` / `PgUp` `PgDn` | Scroll | | `g` | Top | -| `c` | Copy response body | +| `c` | Copy the response body | | `Esc` / `q` / `Enter` | Back to the list | -With `includeHeaders: true`, kportal replaces these header values with `[REDACTED]` and keeps the name: `Authorization`, `Cookie`, `Set-Cookie`, `X-Api-Key`, `X-Auth-Token`, `X-Csrf-Token`, `Proxy-Authorization`, `X-Access-Token`, and any name containing `token`, `secret`, `password` or `apikey`. This cannot be turned off. +#### Sensitive header redaction -### Benchmark +With `includeHeaders: true`, kportal replaces these header values with `[REDACTED]` and keeps the name: `Authorization`, `Cookie`, `Set-Cookie`, `X-Api-Key`, `X-Auth-Token`, `X-Csrf-Token`, `Proxy-Authorization`, `X-Access-Token`, and any name containing `token`, `secret`, `password` or `apikey` (any case). You cannot turn this off. Source: `internal/httplog/proxy.go`. -Press `b` on a forward. Set the URL path (default `/`), HTTP method, concurrency and request count. +### Benchmarking -### Reload +Press `b` on a forward. Set the URL path (default `/`), the HTTP method, the concurrency and the request count. The wizard starts at 10 workers and 100 requests. + +The results screen has this layout (`x.xx` marks a measured value): + +```text +Total Requests: 100 +Successful: 100 (100.0%) +Failed: 0 + +Latency (ms) + Min: x.xx + Max: x.xx + Avg: x.xx + P50: x.xx + P95: x.xx + P99: x.xx + +Throughput + Requests/sec: x.xx + Bytes read: n + +Status Codes + 200: n +``` + +Only 2xx responses count as successful. + +### Hot reload kportal reloads when the config file changes. To reload by hand: @@ -376,14 +554,33 @@ kportal reloads when the config file changes. To reload by hand: kill -HUP $(pgrep kportal) ``` -`Ctrl+C` and `SIGTERM` shut down cleanly. +New forwards start, removed forwards stop and unchanged forwards keep running. An invalid config is rejected and the previous one stays active. -### Import from kftray +### Port conflict detection + +kportal checks that local ports are free at startup and on reload. The report names the process that holds a busy port. kportal finds it with `lsof` on Linux and macOS and `tasklist` on Windows. A reload that would bind a busy port is rejected and the previous config stays active. + +Conflicts are only reported between forwards that can run together. See [Select contexts](#select-contexts). + +### Retry strategy + +Exponential backoff with 10% jitter: 1s, 2s, 4s, 8s, then 10s (cap). Retries continue until the connection succeeds. + +## Migration from kftray ```bash kportal --convert configs.json --convert-output .kportal.yaml ``` +`-convert-output` defaults to `.kportal.yaml`. + +## Signal handling + +| Signal | Effect | +|--------|--------| +| `Ctrl+C`, `SIGTERM` | Graceful shutdown | +| `SIGHUP` | Reload the configuration | + ## Troubleshooting Port already in use: @@ -399,7 +596,7 @@ Connection refused: 2. Check the port: `kubectl describe pod ` 3. Check the service endpoints: `kubectl get endpoints ` -Context not found: list your contexts with `kubectl config get-contexts`. Names with `@`, `.`, `:` or `/` (for example `admin@home` or an EKS ARN) are valid. If `--context` reports `unknown context "..."`, the name is missing from `contexts[].name` in the config file. +Context not found: list your contexts with `kubectl config get-contexts`. kportal reports `context not found in kubeconfig` when a `contexts[].name` value is not in your kubeconfig. Names with `@`, `.`, `:` or `/` (for example `admin@home` or an EKS ARN) are valid. If `--context` reports `unknown context`, the name is missing from `contexts[].name` in the config file. The add wizard shows a namespace prompt instead of a list: the cluster refused or returned an empty namespace list. Type the namespace name. @@ -429,3 +626,9 @@ MIT. See [LICENSE](LICENSE). ## Credits [Bubble Tea](https://github.com/charmbracelet/bubbletea), [Lipgloss](https://github.com/charmbracelet/lipgloss), [client-go](https://github.com/kubernetes/client-go). [kftray](https://github.com/hcavarsan/kftray) was the inspiration. + +## Links + +- [Website](https://lukaszraczylo.github.io/kportal) +- [Issues](https://github.com/lukaszraczylo/kportal/issues) +- [Releases](https://github.com/lukaszraczylo/kportal/releases) diff --git a/docs/index.html b/docs/index.html index 6dbaddd..519f91f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,417 +1,1215 @@ - + kportal: run Kubernetes port-forwards from one YAML file - - - + + -
+ + + -
-

Run Kubernetes port-forwards from one YAML file

-

kportal starts every forward listed in .kportal.yaml, shows their status in a terminal UI, reconnects dropped forwards and reloads the file when it changes. It also runs without the UI.

+
-
brew install --cask lukaszraczylo/taps/kportal
+ +
+
+
+

Kubernetes port-forward manager

+

Run all your Kubernetes port-forwards from one YAML file

+

kportal starts every forward listed in .kportal.yaml, shows live status in a terminal UI, reconnects dropped forwards and reloads the file when it changes. It also runs without the UI.

+
Homebrew, macOS
brew install --cask lukaszraczylo/taps/kportal
+

Linux, Windows and other install methods: Install.

+ +
+
Licence
MIT
+
Language
Go
+
Platforms
Linux, macOS, Windows
+
Architectures
amd64, arm64
+
+
+
+
kportal v0.11.2 - Port Forwarding Status
 
-  
- Release - License - Go Report Card +ALIAS RESOURCE REMOTE LOCAL STATUS +> prod-api service/api 8080 8080 ● Active + prod-db service/postgres 5432 5432 ● Active + prometheus pod 9090 9090 ○ Starting + staging-http pod/myapp 8080 8081 ◐ Reconnecting + staging-met… pod/myapp 9090 9091 ● Active + +↑↓/jk: Navigate PgUp/Dn: Page Space: Toggle n: New e: Edit +d: Delete b: Bench l: Logs q: Quit
+

Example data. Column set from tui.columns: alias, resource, remote, local, status. Status strings and key bar match the source.

+
+
- kportal terminal UI listing port-forwards with their status + +
+
+
+

Interface

+

One table for every forward

+

Each row is one forward. Move with the arrow keys or j and k, toggle with Space, and add, edit or delete forwards without a restart.

+
+
+ kportal terminal UI with a table of ten port-forwards in the home context, all showing the Active status +
Screenshot from kportal v0.2.17. The current key bar also lists PgUp/PgDn, b (Bench) and l (Logs).
+
+
+
-
-

Features

-
- + +
+
+
+

Features

+

What kportal does

+

Every snippet below comes from the repository: .kportal.yaml, the UI source or the install script.

+
+ + +
+
+

Configuration

+

Many forwards, one file

+

Group forwards by context and namespace. Each forward names a resource, a remote port and a local port.

+
    +
  • service/<name> forwards to a service.
  • +
  • pod/<name> matches an exact pod name or a name prefix.
  • +
  • pod with selector picks a pod by label.
  • +
  • protocol is optional. Only tcp is accepted.
  • +
+
+
+
.kportal.yaml
contexts:
+  - name: production
+    namespaces:
+      - name: default
+        forwards:
+          - resource: service/api
+            protocol: tcp
+            port: 8080
+            localPort: 8080
+            alias: prod-api
+            httpLog: true
+
+          - resource: service/postgres
+            protocol: tcp
+            port: 5432
+            localPort: 5432
+            alias: prod-db
+
+      - name: monitoring
+        forwards:
+          - resource: pod
+            selector: app=prometheus
+            protocol: tcp
+            port: 9090
+            localPort: 9090
+            alias: prometheus
+
+  - name: staging
+    namespaces:
+      - name: default
+        forwards:
+          - resource: pod/myapp   # prefix match survives pod restarts
+            protocol: tcp
+            port: 8080
+            localPort: 8081
+            alias: staging-http
+
+
+ + +
+
+

Terminal UI

+

Add, edit and delete forwards in the UI

+

Press n to add, e to edit, d to delete and Space to toggle a forward. The add wizard writes the change to the config file.

+

If the cluster does not let you list namespaces, the wizard asks you to type the namespace instead.

+

On the confirmation step, h toggles httpLog. Advanced httpLog keys that you set in YAML stay unchanged.

+
+
+
Enter Namespace:
+
+Could not list namespaces (<error from the cluster>).
+Type the namespace name instead.
+
+Namespace: backend█
+

Text from internal/ui/wizard_views.go. The error in parentheses comes from the cluster.

+
+
+ + +
+
+

Terminal UI

+

Choose the table columns

+

tui.columns sets which columns the table shows, their order and their width. List order is render order. Unlisted columns are hidden.

+

Valid names: context, namespace, alias, type, resource, remote, local, status. width is the maximum text width, 1 to 200.

+

The setting applies to the UI and to the -v table, and it reloads with the file.

+
+
+
.kportal.yaml
tui:
+  columns:
+    - name: alias
+      width: 30      # optional, 1-200
+    - name: resource
+    - name: local
+    - name: status
+
+
+ + +
+
+

Reliability

+

Reconnect with backoff

+

When a forward drops, kportal retries with exponential backoff. The delay starts at 1 second, doubles up to a 10 second cap and carries 10% jitter. Retries do not stop.

+

Forwards by pod prefix or label selector connect to the new pod after a restart.

+
+
+
attempt   delay (plus or minus 10% jitter)
+1         1s
+2         2s
+3         4s
+4         8s
+5+        10s   (cap)
+
+
+ + +
+
+

Reliability

+

Health checks find stale connections

+

tcp-dial opens a TCP connection to the local port. data-transfer also tries to read data through the tunnel, and is the default.

+

A connection older than maxConnectionAge reconnects only when it is also idle, so a running transfer is not cut. A connection idle longer than maxIdleTime counts as stale.

+

The values shown are the defaults.

+
+
+
.kportal.yaml
healthCheck:
+  interval: "3s"
+  timeout: "2s"
+  method: "data-transfer"  # or tcp-dial
+  maxConnectionAge: "25m"
+  maxIdleTime: "10m"
+
+reliability:
+  tcpKeepalive: "30s"
+  dialTimeout: "30s"
+  retryOnStale: true
+  watchdogPeriod: "30s"
+
+
+ + +
+
+

Operations

+

Hot reload and port-conflict checks

+

kportal reloads when the config file changes. SIGHUP reloads by hand. New forwards start, removed forwards stop and unchanged forwards keep running.

+

An invalid config is rejected and the previous one stays active. A reload that would bind a busy local port is rejected too.

+

The conflict report names the process that holds the port (found with lsof on Linux and macOS, tasklist on Windows).

+
+
+
manual reload
kill -HUP $(pgrep kportal)
+
Configuration diff: 1 to add, 0 to remove, 4 to keep
+
+Config change rejected due to port conflicts:
+  ...
+Keeping previous configuration active
+
+
+ + +
+
+

Contexts

+

Forward only the contexts you name

+

By default kportal forwards every context in the file. --context limits the run. Repeat the flag or separate names with commas.

+

Port conflicts are only reported between forwards that can run together. Contexts that you never select together can reuse a localPort.

+

An unknown name is an error that lists the contexts in the file. Those are the contexts[].name values, not every context in your kubeconfig.

+
+
+
shell
kportal --context development-team-a
+kportal --context team-a --context team-b
+kportal --context team-a,team-b
+
.kportal.yaml: same local port in two clusters
contexts:
+  - name: development-team-a
+    namespaces:
+      - name: rate-service
+        forwards:
+          - &rate-service
+            resource: pod/rpc-server
+            protocol: tcp
+            port: 50051
+            localPort: 3004
+            alias: rate-service
+
+  - name: development-team-b
+    namespaces:
+      - name: rate-service
+        forwards:
+          - <<: *rate-service   # same local port, no conflict
+

With no --context, or with both contexts named, the duplicate port is an error.

+
+
+ + +
+
+

Bulk setup

+

Generate forwards from a cluster

+

kportal generate lists the services in a context and appends the ones you pick to the config file.

+
    +
  1. Select namespaces: Space toggles, a toggles all, / filters.
  2. +
  3. Select services. Services already in the config are locked and non-TCP ports are skipped.
  4. +
  5. Pick the first local port. The default is 10000 and the minimum is 1024. Ports are assigned in order and skip ports in use.
  6. +
+

Enter on the last step saves. b goes back and Esc cancels.

+
+
+
shell
kportal generate --context=my-cluster
+kportal generate --context=my-cluster --config=/path/to/.kportal.yaml
+kportal generate --context=my-cluster --dry-run
+
[dry-run] Would add 2 forwards to .kportal.yaml
+  10000 → my-cluster/default/service/api:8080
+  10001 → my-cluster/default/service/postgres:5432
+
+
+ + +
+
+

Debugging

+

HTTP request log per forward

+

Set httpLog on a forward and press l to see its requests and responses. Open an entry with Enter to read headers, bodies and timing.

+

JSON bodies are pretty-printed, gzip and deflate bodies are decoded and binary bodies show a placeholder. f cycles the filter (All, Non-2xx, Errors), / searches path or method and c in the detail view copies the response body.

+

maxBodySize defaults to 1 MB when you omit it or set a value of 0 or less.

+
+
+
.kportal.yaml
forwards:
+  - resource: service/api
+    port: 8080
+    localPort: 8080
+    httpLog:
+      enabled: true
+      includeHeaders: true   # sensitive values are redacted
+      maxBodySize: 65536     # bytes
+      filterPath: "/api/"    # log only paths containing this text
+      logFile: "api.log"     # also append entries to this file
+
  TIME        METHOD   STATUS  LATENCY   PATH
+  ────────────────────────────────────────────────
+  14:02:11    GET      200     12ms      /api/users
+  14:02:12    POST     201     48ms      /api/users
+  14:02:15    GET      404     9ms       /api/users/42
+
+
+ + +
+
+

Security

+

Sensitive header values are redacted

+

With includeHeaders: true, kportal replaces the value of sensitive headers with [REDACTED] and keeps the header name. You cannot turn this off.

+

You see that an Authorization header was present without seeing its value.

+
+
+

Redacted by name:

+
    +
  • Authorization
  • Proxy-Authorization
  • Cookie
  • Set-Cookie
  • X-Api-Key
  • X-Auth-Token
  • X-Csrf-Token
  • X-Access-Token
  • +
+

Redacted when the name contains (any case):

+
    +
  • token
  • secret
  • password
  • apikey
  • +
+

Source: internal/httplog/proxy.go.

+
+
+ + +
+
+

Debugging

+

Benchmark a forward

+

Press b on a forward. Set the URL path (default /), the HTTP method, the concurrency and the request count. The wizard starts at 10 workers and 100 requests.

+

The results screen reports totals, latency in milliseconds, requests per second, bytes read and the status code distribution. Only 2xx responses count as successful.

+
+
+
Total Requests:  100
+Successful:      100 (100.0%)
+Failed:          0
+
+Latency (ms)
+  Min:    x.xx
+  Max:    x.xx
+  Avg:    x.xx
+  P50:    x.xx
+  P95:    x.xx
+  P99:    x.xx
+
+Throughput
+  Requests/sec:  x.xx
+  Bytes read:    n
+
+Status Codes
+  200: n
+

Layout from internal/ui/wizard_views.go. The page shows no measured numbers.

+
+
+ + +
+
+

Networking

+

mDNS hostnames

+

With mdns.enabled: true, each forward is reachable as <alias>.local. Without an alias, the resource name is used: service/redis becomes redis.local.

+

A bare pod with a selector and no alias is not published. Services register as _kportal._tcp.

+
+
+
.kportal.yaml
mdns:
+  enabled: true
+
+contexts:
+  - name: production
+    namespaces:
+      - name: default
+        forwards:
+          - resource: service/postgres
+            port: 5432
+            localPort: 5432
+            alias: prod-db   # reachable as prod-db.local:5432
+
check registration
dns-sd -B _kportal._tcp local       # macOS
+avahi-browse -t _kportal._tcp       # Linux
+
+
+ + +
+
+

Automation

+

Headless mode and config validation

+

-headless runs without the UI and writes logs to stderr. -v sets the log level, and -log-format json switches the format from text.

+

-check validates the config and exits. Ctrl+C and SIGTERM shut down cleanly.

+

Without -headless, -v prints a plain-text table that redraws.

+
+
+
shell
kportal -headless -v 2>kportal.log &
+kportal -headless -log-format json
+kportal -check
+kportal -c /path/to/config.yaml
+
+
+ + +
+
+

Tooling

+

Import from kftray, shell completion, updates

+

-convert turns a kftray JSON file into kportal YAML. -convert-output sets the output file (default .kportal.yaml).

+

kportal completion prints a bash, zsh or fish script, or installs it. Without --shell, kportal detects the shell.

+

-update checks for a newer release and -version prints the version.

+
+
+
shell
kportal --convert configs.json --convert-output .kportal.yaml
+
+kportal completion --shell zsh            # print the script
+kportal completion --shell zsh --install  # install it
+kportal completion --uninstall            # remove it
+
+kportal -update
+kportal -version
+
+
+ + +
+
+

Supply chain

+

Installer that checks what it downloads

+

The install script downloads kportal-<version>-checksums.txt from the same release and verifies the archive SHA-256. It refuses to install on a mismatch.

+

If cosign is on PATH, it also verifies the keyless signature of the checksums file. Install options list the steps to verify a release by hand.

+
+
+
shell
curl -fsSL https://raw.githubusercontent.com/lukaszraczylo/kportal/main/install.sh | bash
+
+# download and verify only
+curl -fsSL https://raw.githubusercontent.com/lukaszraczylo/kportal/main/install.sh | DRY_RUN=1 bash
+
+
+
+
+ + +
+
+
+

Comparison

+

kportal next to other port-forward tools

+

The table lists what each project documents. A cell reads "Not documented" when the project's README and docs do not say.

+
+
+

Compared on 2026-10-02 against each project's README and docs. Check their repositories for changes. The kportal column comes from this repository's code.

+
+ +
+
+ + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + +
Feature comparison of kportal, kubectl port-forward, k9s, kubefwd and kftray
Featurekportalkubectl port-forwardk9skubefwdkftray
Forwardsservice/<name>, pod/<name> (exact name or prefix), or pod with a label selector. TCP only.
Terminal UIAdd, edit, delete and toggle forwards without a restart. The add wizard writes the config file.
Table columnstui.columns sets which columns the forwards table shows, their order and width. It applies to the UI and the -verbose table.
Add wizardIf the cluster does not allow listing namespaces, the wizard asks you to type the namespace.
ReconnectExponential backoff: 1s, 2s, 4s, 8s, then 10s, with 10% jitter. It does not stop retrying.
Pod restartsForwards by pod prefix or selector connect to the new pod.
Health checkstcp-dial or data-transfer, every 3s by default. Stale connections are reconnected by age and idle time.
Hot reloadFile watcher and SIGHUP. An invalid config is rejected and the previous one stays active.
ContextsSeveral contexts and namespaces in one file. --context selects which contexts run.
Port conflictsStartup and reload checks report a busy local port and the process that holds it.
mDNSWith mdns.enabled: true, each forward is reachable as <alias>.local.
HTTP logPer-forward request log with details, filter, search, JSON formatting, gzip decoding and body copy. Sensitive header values are redacted.
BenchmarkHTTP requests through a forward. Reports min, max and average latency, P50, P95, P99, requests per second and status codes.
Generatekportal generate lists cluster services and appends the ones you pick, with consecutive local ports.
Headless-headless runs without the UI and logs to stderr. -log-format json switches the log format.
Import--convert turns a kftray JSON file into kportal YAML.
Shell completionkportal completion prints or installs bash, zsh and fish completions.
Many forwards in one config fileYesNoPartialannotations configure forwards; no forward list filePartialexample.fwdconf.yml existsYes
Automatic reconnectYesNot documentedNot documentedYesYes
Follows pod restarts by label or name prefixYesNot documentedNot documentedYeslabel filterYes
Terminal UI of all forwardsYesNoYesPortForward view, alias pf; forwards last for the sessionYesYes
GUINoNoNoNoYes
Config hot reloadYesfile watcher and SIGHUPNoPartialbenchmarks.yaml onlyNot documentedNot documented
HTTP request and response loggingYesNoNot documentedNot documentedYes
Health checksYestcp-dial or data-transferNot documentedNot documentedNot documentedPartialREADME says it tracks pod health
mDNS or friendly hostnamesYesmDNS, <alias>.localNoNoPartialedits /etc/hosts, not mDNS; needs rootNot documented
Built-in benchmarkYesNoYeshey, key bNot documentedNot documented
Imports another tool's configYeskftray JSONNoNoNot documentedPartialJSON, GitHub repos, service annotations; not other tools' formats
Several contexts in one configYesNoPartialPartialNot documented
Local port conflict detectionYesreports the holding processPartial:remotePort picks a free portNot documentedYesNot documented
PlatformsLinux, macOS, WindowsNot documentedmacOS, Linux, WindowsmacOS, Linux, WindowsmacOS, Linux, Windows
LicenceMITNot documentedApache-2.0Apache-2.0GPL-3.0
LanguageGoNot documentedGoGoRust (Tauri, Ratatui)
Latest release (2026-10-02)Releases pageNot documentedv0.51.0 (2026-06-06)v1.25.16 (2026-06-20)v0.29.2 (2026-09-29)
-
+

On narrow screens, scroll the table sideways. The first column stays in place.

-
-

Install

-

Homebrew (macOS)

-
brew install --cask lukaszraczylo/taps/kportal
-

Install script

-
curl -fsSL https://raw.githubusercontent.com/lukaszraczylo/kportal/main/install.sh | bash
-

The script verifies the archive SHA-256 against the release checksums file. If cosign is on PATH, it also verifies the keyless signature. Set DRY_RUN=1 to download and verify without installing. Set SKIP_COSIGN=1 to skip the cosign check.

-

Manual download

-

The releases page has archives for Linux, macOS and Windows on amd64 and arm64.

-

Build from source

-
git clone https://github.com/lukaszraczylo/kportal.git
+    
+
+

When another tool fits better

+
    +
  • kubectl port-forward for a one-off forward. It needs no config file and ships with kubectl.
  • +
  • k9s for general cluster browsing and its built-in benchmark.
  • +
  • kubefwd to forward every service in a namespace with local DNS names.
  • +
  • kftray for a tray GUI, shared JSON configs and UDP.
  • +
+

Kube Forwarder is a GUI-only forwarder. Its last release was 2019-08-14.

+
+ +
+ +
+ + +
+
+
+

Install

+

Install kportal

+

Pick one method. Release archives cover Linux, macOS and Windows on amd64 and arm64.

+
+ +
+
+

Homebrew (macOS)

+
brew install --cask lukaszraczylo/taps/kportal
+

If you installed the formula (brew install lukaszraczylo/taps/kportal) earlier, run brew uninstall kportal first.

+
+ +
+

Install script

+
curl -fsSL https://raw.githubusercontent.com/lukaszraczylo/kportal/main/install.sh | bash
+

The script downloads kportal-<version>-checksums.txt from the same release and verifies the archive SHA-256. If cosign is on PATH, it also verifies the keyless signature of the checksums file.

+
+ + + + + + + + + +
VariableEffect
DRY_RUN=1Download and verify only. Do not install.
SKIP_COSIGN=1Skip the cosign check. The SHA-256 check still runs.
INSTALL_DIRTarget directory. Default /usr/local/bin.
KPORTAL_VERSIONInstall a specific version, for example 1.2.3, instead of the latest.
COSIGN_CERT_IDENTITY_REGEXPCertificate identity to accept. Set it if you fork the release pipeline.
+
+
example
curl -fsSL https://raw.githubusercontent.com/lukaszraczylo/kportal/main/install.sh | DRY_RUN=1 bash
+
+ +
+

Manual download

+

The releases page has one archive per platform. Archives are named kportal-<version>-<os>-<arch>.tar.gz, and .zip on Windows. Each release also has a kportal-<version>-checksums.txt file.

+
+ +
+

Build from source

+
git clone https://github.com/lukaszraczylo/kportal.git
 cd kportal
-make build && make install
-

The build needs Go 1.26 or later.

-
+make build && make install
+

The build needs Go 1.26 or later (see go.mod). make install copies the binary to /opt/homebrew/bin if that directory exists, otherwise to ~/.local/bin on macOS and Linux.

+ -
-

Usage

-

Quick start

-

Create .kportal.yaml, then run kportal.

-
contexts:
-  - name: production
-    namespaces:
-      - name: backend
-        forwards:
-          - resource: service/postgres
-            protocol: tcp
-            port: 5432
-            localPort: 5432
-            alias: prod-db
+      
+

Verify a release signature

+

Release checksums are signed with cosign using keyless signing. Download the checksums file and its sigstore bundle from the release, then run:

+
cosign verify-blob \
+  --certificate-identity-regexp "^https://github\.com/lukaszraczylo/shared-actions/\.github/workflows/go-release\.yaml@refs/heads/main$" \
+  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
+  --bundle "kportal-<version>-checksums.txt.sigstore.json" \
+  kportal-<version>-checksums.txt
+

Replace <version> with the release version, for example 1.2.3. Then check the archive against the verified checksums file with shasum -a 256 or sha256sum.

+
+ + +
- - resource: service/api - protocol: tcp - port: 8080 - localPort: 8080 - alias: api - httpLog: true + +
+
+
+

Quick start

+

From install to first forward

+

Create .kportal.yaml in the directory where you run kportal, then start it.

+
+
+
+
.kportal.yaml
contexts:
+  - name: production
+    namespaces:
+      - name: backend
+        forwards:
+          - resource: service/postgres
+            protocol: tcp
+            port: 5432
+            localPort: 5432
+            alias: prod-db
 
-    

Commands and flags

-
+ - resource: service/api + protocol: tcp + port: 8080 + localPort: 8080 + alias: api + httpLog: true
+
run
kportal
+

Each name under contexts must exist in your kubeconfig. List them with kubectl config get-contexts.

+
+
+

Keyboard shortcuts

+

Forwards list

+
+ + + + + + + + + + + + +
KeyAction
↑ ↓ / j kMove the selection
PgUp PgDn / Ctrl+u Ctrl+dMove by 10 rows
Space / EnterToggle the forward
nAdd a forward
eEdit the forward
dDelete the forward (asks to confirm)
bBenchmark the forward
lOpen the HTTP log
q / Ctrl+cQuit
+

HTTP log, list view

+
+ + + + + + + + + + + +
KeyAction
↑ ↓Move
EnterOpen details
g / GTop / bottom
aToggle auto-scroll
fCycle filter: All, Non-2xx, Errors
/Search path or method
cClear filters
qClose
+

HTTP log, detail view

+
+ + + + + + + +
KeyAction
↑ ↓ / PgUp PgDnScroll
gTop
cCopy the response body
Esc / q / EnterBack to the list
+
+
+
+
+ + +
+
+
+

Documentation

+

Reference

+

Command line, config keys and status values.

+
+ +

Command line

+
- + - + - + - + + - - + +
Command or flagEffect
Command or flagEffect
kportalStart the terminal UI.
-c <file>Config file. Default .kportal.yaml.
-vVerbose logging.
-vVerbose logging. In headless mode it sets the log level only.
-headlessNo UI. Logs go to stderr.
-checkValidate the config and exit.
-context <name>Forward only the named contexts. Repeatable, or comma-separated.
-context <name>Forward only the named contexts. Repeatable and comma-separated.
-log-format text|jsonLog format. Default text.
-convert <file>Convert a kftray JSON file. -convert-output sets the output, default .kportal.yaml.
-convert <file>Convert a kftray JSON file to kportal YAML.
-convert-output <file>Output of -convert. Default .kportal.yaml.
-updateCheck for updates and exit.
-versionPrint the version and exit.
kportal generateAdd forwards from a cluster. Needs --context. --config picks the file and --dry-run only prints.
kportal completionPrint a completion script. --shell bash|zsh|fish, --install, --uninstall.
kportal generateAdd forwards from a cluster. Flags: --context (required), --config, --dry-run.
kportal completionPrint or install a shell completion script. Flags: --shell, --install, --uninstall.
+

Each flag also accepts the double-dash form, for example --check.

-

Examples

-
kportal -check
-kportal -c /path/to/config.yaml
-kportal --context development-team-a
-kportal -headless -v 2>kportal.log &
-kportal generate --context=my-cluster --dry-run
-kportal completion --shell zsh --install
-

With --context, contexts you never select together can reuse a localPort. Without it, a duplicate local port is an error.

-
- -
-

Keys

-
-
-

Forwards list

-
- - - - - - - - - - - - -
Up Down / j kMove
PgUp PgDnMove by 10 rows
Space / EnterToggle forward
nAdd forward
eEdit forward
dDelete forward
bBenchmark
lHTTP log
qQuit
-
-

In the add and edit wizard, h on the confirmation step toggles httpLog.

-
-
-

Status

-
- - - - - - - - -
● ActiveConnection healthy
○ StartingFirst connection. Failed checks show Starting for 10s.
◐ ReconnectingReconnecting after a failure
✗ ErrorConnection failed
○ DisabledTurned off in the UI
-
-
-
-
- -
-

Configuration

-

Forward fields

-
+

Forward options

+
- + - + - - + +
FieldRequiredDescription
KeyRequiredDescription
resourceYesservice/<name>, pod/<name>, or pod with selector
resourceYesservice/<name>, pod/<name> (exact name or prefix), or pod with selector
protocolNotcp. No other value is accepted.
portYesRemote port
localPortYesLocal port
aliasNoDisplay name and mDNS hostname
selectorWith bare podLabel selector, for example app=nginx. Not allowed with pod/<name>.
httpLogNotrue, or a map of options
selectorWith bare podLabel selector. Not allowed with pod/<name>.
httpLogNotrue, or a map with enabled, includeHeaders, maxBodySize, filterPath, logFile
-
-
-

Health checks and reliability

-
healthCheck:
-  interval: "3s"
-  timeout: "2s"
-  method: "data-transfer"
-  maxConnectionAge: "25m"
-  maxIdleTime: "10m"
-
-reliability:
-  tcpKeepalive: "30s"
-  dialTimeout: "30s"
-  retryOnStale: true
-  watchdogPeriod: "30s"
-

These are the defaults. method is tcp-dial or data-transfer. A connection past maxConnectionAge reconnects only when it is also idle.

-
-
-

mDNS

-
mdns:
-  enabled: true
-
-contexts:
-  - name: production
-    namespaces:
-      - name: default
-        forwards:
-          - resource: service/postgres
-            port: 5432
-            localPort: 5432
-            alias: db   # db.local
-

Without an alias, service/redis publishes redis.local.

-
+

Top-level keys

+
+ + + + + + + + + + + + + + + +
KeyDefaultDescription
healthCheck.interval3sTime between checks
healthCheck.timeout2sTimeout of one check
healthCheck.methoddata-transfertcp-dial or data-transfer
healthCheck.maxConnectionAge25mReconnect an idle connection older than this
healthCheck.maxIdleTime10mA connection idle longer than this is stale
reliability.tcpKeepalive30sTCP keepalive interval
reliability.dialTimeout30sConnection dial timeout
reliability.retryOnStaletrueReconnect when a stale connection is detected
reliability.watchdogPeriod30sInterval of the check for hung workers
mdns.enabledoffPublish <alias>.local for each forward
tui.columnsall columnsList of name and optional width
-

Table columns

-
tui:
-  columns:
-    - name: alias
-      width: 30      # optional, 1-200
-    - name: resource
-    - name: local
-    - name: status
-

Valid names: context, namespace, alias, type, resource, remote, local, status. List order is render order and unlisted columns are hidden. Without tui, the table keeps its default columns. The setting applies to the UI and the -verbose table.

+

Status indicators

+
+ + + + + + + + + +
IndicatorMeaning
● ActiveConnection healthy
○ StartingFirst connection. Failed health checks show Starting for the first 10 seconds.
◐ ReconnectingReconnecting after a failure
✗ ErrorConnection failed
○ DisabledTurned off in the UI
+
+

The -v table uses the same words. When stdout is not a terminal, it draws ✓ Active, ⋯ Starting, ↻ Reconnecting and ✗ Error.

-

Reload

-

kportal reloads when the file changes. To reload by hand, run kill -HUP $(pgrep kportal).

-
+

Migrate from kftray

+

Convert a kftray JSON export to kportal YAML:

+
kportal --convert configs.json --convert-output .kportal.yaml
-
-

HTTP log and benchmark

-

Press l on a forward that has httpLog set. The list shows TIME, METHOD, STATUS, LATENCY and PATH.

-
httpLog:
-  enabled: true
-  includeHeaders: true   # sensitive values are redacted
-  maxBodySize: 65536     # bytes, 0 = unlimited
-  filterPath: "/api/"    # log only paths containing this text
-  logFile: "api.log"     # also append entries to this file
-
+

Signals

+
+ + + + + + +
SignalEffect
Ctrl+C, SIGTERMGraceful shutdown
SIGHUPReload the configuration
+
+
+
+ + +
+
+
+

Troubleshooting

+

Common failures

+
+
-

List view

-
    -
  • Enter opens details
  • -
  • g / G jump to top or bottom
  • -
  • a toggles auto-scroll
  • -
  • f cycles the filter: All, Non-2xx, Errors
  • -
  • / searches path or method
  • -
  • c clears filters, q closes
  • -
+

Port already in use

+
lsof -i :<port>
+kill <pid>
+

Connection refused

+
    +
  1. Check the pod: kubectl get pods -n <namespace>
  2. +
  3. Check the port: kubectl describe pod <pod>
  4. +
  5. Check the service endpoints: kubectl get endpoints <service>
  6. +
-

Detail view

-
    -
  • Headers, bodies and timing
  • -
  • JSON is pretty-printed, gzip and deflate are decoded
  • -
  • c copies the response body
  • -
  • Esc, q or Enter returns to the list
  • -
+

Context not found

+
kubectl config get-contexts
+

kportal reports context <name> not found in kubeconfig when a contexts[].name value is not in your kubeconfig. Names with @, ., : or / (for example admin@home or an EKS ARN) are valid. If --context reports unknown context, the name is missing from contexts[].name in the config file.

+

Namespace prompt instead of a list

+

The cluster refused the namespace list or returned an empty one. Type the namespace name.

-

With includeHeaders: true, values of Authorization, Cookie, Set-Cookie, X-Api-Key, X-Auth-Token, X-Csrf-Token, Proxy-Authorization, X-Access-Token and any header whose name contains token, secret, password or apikey become [REDACTED]. This cannot be turned off.

-

Benchmark

-

Press b on a forward. Set the URL path (default /), method, concurrency and request count.

-
+ + + + +
+
+
+

FAQ

+

Questions and answers

+
+
+
Does kportal support UDP?

No. Only tcp is accepted for protocol, and kportal generate skips non-TCP service ports.

+
Can kportal forward to a deployment?

No. Valid resource types are pod and service. To reach a deployment, use pod with a label selector.

+
Does a config edit need a restart?

No. kportal reloads when the file changes. An invalid file is rejected and the previous config stays active.

+
How do I run kportal on a server or in a script?

Use -headless. Add -log-format json for structured logs and -check in CI to validate the file.

+
Does the HTTP log expose my tokens?

No. The value of Authorization, Cookie and other sensitive headers is replaced with [REDACTED]. See Features for the full list.

+
Does kportal collect data?

On startup it sends one anonymous adoption ping with the project name, version and timestamp. The timeout is 2 seconds. Set DO_NOT_TRACK=1, OSS_TELEMETRY_DISABLED=1 or KPORTAL_DISABLE_TELEMETRY=1 to opt out. Details: oss-telemetry.

+
+
+
-
-

More

-

The README covers troubleshooting, kftray import and telemetry opt-out. Release history is in the changelog.

-
-