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
Features
| Forwards | service/<name>, pod/<name> (exact name or prefix), or pod with a label selector. TCP only. |
|---|---|
| Terminal UI | Add, edit, delete and toggle 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 width. It applies to the UI and the -verbose table. |
| Add wizard | If the cluster does not allow listing namespaces, the wizard asks you to type the namespace. |
| Reconnect | 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 the new pod. |
| Health checks | tcp-dial or data-transfer, every 3s by default. Stale connections are reconnected by age and idle time. |
| Hot reload | File watcher and SIGHUP. An invalid config is rejected and the previous one stays active. |
| Contexts | Several 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 <alias>.local. |
| HTTP log | Per-forward request log with details, filter, search, JSON formatting, gzip decoding and body copy. Sensitive header values are redacted. |
| Benchmark | HTTP requests through a forward. Reports min, max and average latency, P50, P95, P99, requests per second and status codes. |
| Generate | kportal 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 completion | kportal completion prints or installs bash, zsh and fish completions. |
Install
Homebrew (macOS)
brew install --cask lukaszraczylo/taps/kportalInstall script
curl -fsSL https://raw.githubusercontent.com/lukaszraczylo/kportal/main/install.sh | bashThe 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
cd kportal
make build && make install
The build needs Go 1.26 or later.
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
- resource: service/api
protocol: tcp
port: 8080
localPort: 8080
alias: api
httpLog: true
Commands and flags
| Command or flag | Effect |
|---|---|
kportal | Start the terminal UI. |
-c <file> | Config file. Default .kportal.yaml. |
-v | Verbose logging. |
-headless | No UI. Logs go to stderr. |
-check | Validate the config and exit. |
-context <name> | Forward only the named contexts. Repeatable, or comma-separated. |
-log-format text|json | Log format. Default text. |
-convert <file> | Convert a kftray JSON file. -convert-output sets the output, default .kportal.yaml. |
-update | Check for updates and exit. |
-version | Print the version and exit. |
kportal generate | Add forwards from a cluster. Needs --context. --config picks the file and --dry-run only prints. |
kportal completion | Print a completion script. --shell bash|zsh|fish, --install, --uninstall. |
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 k | Move |
| PgUp PgDn | Move by 10 rows |
| Space / Enter | Toggle forward |
| n | Add forward |
| e | Edit forward |
| d | Delete forward |
| b | Benchmark |
| l | HTTP log |
| q | Quit |
In the add and edit wizard, h on the confirmation step toggles httpLog.
Status
| ● Active | Connection healthy |
| ○ Starting | First connection. Failed checks show Starting for 10s. |
| ◐ Reconnecting | Reconnecting after a failure |
| ✗ Error | Connection failed |
| ○ Disabled | Turned off in the UI |
Configuration
Forward fields
| Field | Required | Description |
|---|---|---|
resource | Yes | service/<name>, pod/<name>, 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, for example app=nginx. Not allowed with pod/<name>. |
httpLog | No | true, or a map of options |
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.
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.
Reload
kportal reloads when the file changes. To reload by hand, run kill -HUP $(pgrep kportal).
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
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
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
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.
More
The README covers troubleshooting, kftray import and telemetry opt-out. Release history is in the changelog.