kportal kportal

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
Release License Go Report Card
kportal terminal UI listing port-forwards with their status

Features

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.

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
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 flagEffect
kportalStart the terminal UI.
-c <file>Config file. Default .kportal.yaml.
-vVerbose logging.
-headlessNo UI. Logs go to stderr.
-checkValidate the config and exit.
-context <name>Forward only the named contexts. Repeatable, or comma-separated.
-log-format text|jsonLog format. Default text.
-convert <file>Convert a kftray JSON file. -convert-output sets the output, 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.

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

FieldRequiredDescription
resourceYesservice/<name>, pod/<name>, 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

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.