port-forward
A terminal app that keeps a set of Kubernetes port-forwards alive — one
config file, one command, one live status view.
Port Forwards
NAME CONTEXT TARGET LOCAL STATUS RESTARTS
▸ argocd dev argocd/argocd-server:443 :8080 running 0
grafana dev monitoring/grafana:80 :3000 running 1
grafana-stage stage monitoring/grafana:80 :3001 restarting 2
q quit r restart selected enter details ↑/↓ select
Why
kubectl port-forward is fine for a quick one-off session, but it does not
survive daily development:
- It exits as soon as its connection to the pod is lost — a rollout, a pod
restart, a dropped backend connection — and never reconnects on its own.
- Forwarding several services means one terminal (or tmux pane) per service,
each running its own
kubectl port-forward.
- There is no consolidated view of what is still alive; you find out a
forward died when your request hangs.
port-forward starts every forward you configure, supervises each one
independently, restarts it automatically when it dies, and renders a live
status table instead of scrolling logs.
It drives kubectl under the hood, so kubeconfig handling, auth plugins,
context selection, and service-to-pod resolution behave exactly like the
tooling you already use.
Install
go install github.com/german-swan/port-forward@latest
Requirements:
kubectl 1.24+ on PATH (used as the port-forward backend).
- Go 1.24+ (only to install or build).
Or build from source:
git clone https://github.com/german-swan/port-forward
cd port-forward
just build # builds bin/port-forward
Create a port-forwards.yaml (start from
port-forwards.example.yaml):
forwards:
# Argo CD UI/API -> https://localhost:8080
- name: argocd # unique display name
context: dev # kubeconfig context; omit to use the current one
namespace: argocd
service: argocd-server # forwarded as svc/<service>
remotePort: 443
localPort: 8080 # must be unique across all forwards
# Grafana -> http://localhost:3000
- name: grafana
context: dev
namespace: monitoring
service: grafana
remotePort: 80
localPort: 3000
# The same service from another cluster on another local port
- name: grafana-stage
context: stage
namespace: monitoring
service: grafana
remotePort: 80
localPort: 3001
Each entry runs the equivalent of:
kubectl --context dev --namespace monitoring port-forward svc/grafana 3000:80
The config is validated before anything starts: unknown YAML keys, missing
fields, invalid ports, and duplicate names or local ports are all rejected
up front.
Use
port-forward # uses ./port-forwards.yaml
port-forward -c ~/forwards.yaml # or any other path
| Key |
Action |
↑/↓, k/j |
select a forward |
enter |
details view (status, last error, recent logs) |
esc |
back to the table |
r |
restart the selected forward |
q, Ctrl-C |
stop all forwards and quit |
The app runs in the terminal's alternate screen and rewrites the view in
place; raw kubectl output is never printed to the terminal. The last 200
log lines per forward are kept in memory and shown in the details view.
Behavior
- Each forward runs
kubectl [--context <ctx>] --namespace <ns> port-forward svc/<service> <local>:<remote> under its own supervisor.
- A forward that exits unexpectedly is restarted after 1s. While a forward
cannot be established at all, the delay doubles up to 30s and the status
escalates from
restarting to error after 3 consecutive failed attempts.
It keeps retrying either way.
Ctrl-C, q, or SIGTERM stop everything: each kubectl process gets
SIGTERM and 3 seconds to exit before being killed, then the terminal is
restored. User-initiated shutdown exits with status 0.
Development
just test # go test ./...
just vet # go vet ./...
just fmt # gofmt -w .
Package layout follows the supervisor/TUI split: internal/config (YAML
loading and validation), internal/forward (kubectl subprocess supervision,
status events), internal/tui (Bubble Tea model and views), internal/ring
(recent-log buffer), and the root package (entrypoint wiring signals, the event
channel, and the TUI together). See SPEC.md for the full
requirements and design.
License
MIT