The persistent, configuration-driven proxy for Kubernetes developers. Stop restarting kubectl port-forward. Start coding.
Project Status Kubeport is an experimental tool developed with AI assistance (Gemini/Claude). While optimized for reliability and ease of use in dev/staging, it has not yet undergone a formal security audit. Users are encouraged to review the networking logic before use in critical production systems.
Standard port-forwarding is brittle. Kubeport transforms flaky tunnels into a stable, automated local development gateway.
- Self-Healing — Dropped connections? Kubeport detects and restarts them instantly with exponential backoff.
- Config-as-Code — Define all your services in one
kubeport.yamland check it into your repo for the whole team. - Lifecycle Hooks — Run shell commands, exec binaries, or fire webhooks when tunnels connect, disconnect, or fail.
- Multi-Port Discovery — Forward every port a service exposes with
ports: all, or pick specific named ports. - Background Daemon — Runs quietly in the background; control everything with simple CLI commands.
- Network Simulation — Inject latency, jitter, and bandwidth throttling to test under degraded conditions.
- Chaos Engineering — Random error injection and latency spikes to validate your app's resilience.
- Zero Cluster Footprint — Pure client-side SPDY tunnels. Nothing deployed to your cluster.
# Install
brew install rbaliyan/tap/kubeport
# Create a config
kubeport config init
# Start all services
kubeport start
# Check what's running
kubeport status$ kubeport status
SERVICE STATUS LOCAL REMOTE POD
My API connected :8080 → :80 my-api-7d4b8c6f9-x2k4m
Redis connected :6379 → :6379 redis-0
Platform/http connected :80 → :80 platform-7f8a9b-q4r2s
Platform/grpc connected :9090 → :9090 platform-7f8a9b-q4r2s
Don't make your teammates guess which ports to forward. Check this into your project root:
# kubeport.yaml
context: my-cluster
namespace: default
services:
- name: Auth API
service: auth-api
local_port: 8080
remote_port: 8080
- name: Postgres
pod: postgres-0
local_port: 5432
remote_port: 5432
namespace: databases
- name: Platform
service: platform-svc
ports: all # auto-discover and forward every port
supervisor:
health_check_interval: 10s # TCP probe frequency
health_check_threshold: 3 # consecutive failures before restart
max_restarts: 0 # 0 = unlimited
hooks:
- name: notify
type: shell
shell:
forward:connected: notify-send "kubeport" "${KUBEPORT_SERVICE} ready on port ${KUBEPORT_LOCAL_PORT}"
forward:failed: notify-send -u critical "kubeport" "${KUBEPORT_SERVICE} failed: ${KUBEPORT_ERROR}"Both YAML and TOML formats are supported. See example.yaml and example.toml.
Shared global config: Use extends to inherit common settings (API key, context, supervisor) from a parent config:
# ~/projects/myapp/kubeport.yaml
extends: ~/.config/kubeport/global.yaml
namespace: myapp
services:
- name: postgres
service: postgres
local_port: 5432
remote_port: 5432See Config Inheritance for full merge semantics.
Each port-forward runs in its own supervised goroutine with:
- TCP health checks every 10s (configurable) to detect silent failures
- Automatic restart with exponential backoff (1s → 30s) and 25% jitter
- Backoff reset if a connection stays healthy for 30+ seconds
- Smart pod selection — prefers Ready pods, skips terminating pods during rollouts
Don't list every port manually. Let kubeport discover them from the Kubernetes API:
services:
# Forward all ports
- name: Platform
service: platform-svc
ports: all
# Pick specific named ports with local overrides
- name: Backend
service: backend-svc
ports:
- name: http
local_port: 8080
- name: grpc
# All ports except metrics, shifted by an offset
- name: Infra
service: infra-svc
ports: all
exclude_ports: [metrics]
local_port_offset: 10000Each discovered port becomes an independent supervised forward (Platform/http, Platform/grpc) with its own health checks and restart tracking.
Kubeport isn't just a tunnel — it's a workflow engine. Use hooks to bridge the gap between your cluster and your local machine.
| Event | When It Fires |
|---|---|
manager:starting |
Before any forwards begin (gate event — can block startup) |
manager:stopped |
All forwards stopped, cleanup complete |
forward:connected |
A tunnel is ready and healthy |
forward:disconnected |
A tunnel dropped (will retry) |
forward:failed |
Max restarts exceeded — permanently failed |
forward:stopped |
A forward was intentionally stopped |
health:check_failed |
A single health-check probe failed |
service:added |
A service was dynamically added |
service:removed |
A service was dynamically removed |
pod:terminating |
Current pod is terminating, preemptive reconnect starting |
Three hook types: shell (sh -c), exec (direct binary), and webhook (HTTP POST).
Gate startup on VPN:
hooks:
- name: vpn-check
type: shell
events: [manager:starting]
fail_mode: closed # block startup if VPN is down
shell:
manager:starting: ./scripts/ensure-vpn.shSlack alerts on failures:
hooks:
- name: slack
type: webhook
events: [forward:failed]
webhook:
url: https://hooks.slack.com/services/T.../B.../xxx
body_template: '{"text": ":warning: ${SERVICE} failed: ${ERROR}"}'See the hooks guide for all options, environment variables, and more examples.
Add, remove, and reload services without restarting the daemon:
# Add a service on the fly (--persist writes it to the config file)
kubeport add --name "Postgres" --pod postgres-0 --remote-port 5432 --local-port 5432 --persist
# Remove a running service
kubeport remove "Postgres"
# Reload after editing the config file
kubeport reload
# Merge services from another file
kubeport apply --file overlay.yamlRun entirely from the command line:
kubeport start --no-config \
--context my-cluster \
--svc "api:svc/my-api:80:8080" \
--svc "redis:pod/redis-0:6379:6379" \
--svc "platform:svc/platform-svc:all"kubeport start # Start daemon in background
kubeport status # Check all forwards
kubeport status --json # Machine-readable output
kubeport watch # Live-refresh status display (updates every 2s)
kubeport fg # Run in foreground (for debugging or containers)
kubeport stop # Graceful shutdown
kubeport restart # Stop + start
kubeport logs # Follow daemon logs
kubeport reload # Sync config changes to running daemon
kubeport mappings # Show K8s DNS → localhost address mappings
kubeport socks # Start SOCKS5 proxy for address translation (default: 127.0.0.1:1080)
kubeport http-proxy # Start HTTP/HTTPS proxy for address translation (default: 127.0.0.1:3128)
kubeport instances # List all running kubeport daemons (useful for diagnosing port conflicts)
kubeport update check # Check whether a newer release is available
kubeport chaos preset slow-network postgres # Inject latency on a live tunnel without restarting
kubeport chaos set postgres --error-rate 0.05 # Apply 5% error injection to a running service
kubeport chaos disable --all # Turn off chaos injection across all services# See all running kubeport daemons (across all config files)
kubeport instances
# Example output:
# PID UPTIME VERSION ROLE ENDPOINT API KEY CONFIG
# 12345 4m32s v0.8.1 primary ~/.config/kubeport/myproject-a3f8b2c1.sock none ~/myproject/kubeport.yaml
# 67890 1h5m v0.8.1 primary ~/.config/kubeport/infra-d9e1f234.sock none ~/infra/kubeport.yaml
# 67891 2m10s v0.8.1 delegate ~/.config/kubeport/edge-f0a1b2c3.sock none ~/edge/kubeport.yamlThe ROLE column is primary for normal daemons and delegate for instances started with --delegate (which forward their services to a primary daemon — see Delegating Services).
Use --offload to add the services from your config to an already-running daemon instead of starting a new one. This is useful when you want to reuse an existing daemon rather than launch a second process.
# Start the primary daemon
kubeport start
# In another project, offload its services to the already-running daemon
# instead of starting a second daemon
kubeport start --config ~/other-project/kubeport.yaml --offload
# The services from other-project/kubeport.yaml are added to the running daemonUse --delegate to start a lease-holder daemon that forwards its services to an existing primary daemon and tears them down on exit. This is similar to --offload (services run on the primary), but with two key differences:
- The delegate stays alive as a separate process, registered in
kubeport instanceswithROLE: delegateandPrimary: <socket>. - When the delegate stops (via
kubeport stopor SIGTERM), it callsReleaseBySourceon the primary to bulk-remove only the services it contributed — leaving everything else untouched.
# Primary daemon already running
kubeport start
# In a different project, run as a delegate that hands its services to the primary
kubeport start --config ~/edge/kubeport.yaml --delegate
# kubeport instances now shows two rows:
# PID ROLE CONFIG
# 12345 primary ~/myproject/kubeport.yaml
# 12399 delegate ~/edge/kubeport.yaml
# Stopping the delegate cleanly removes its contributed services from the primary
kubeport stop --config ~/edge/kubeport.yamlIf no primary daemon is running, --delegate falls back to a regular start and the new instance is registered as primary.
By contrast, auto external-conflict detection (the default kubeport start behaviour) takes a hands-off approach: if another instance is already managing a service with the same name or static local_port, the new instance marks that service as external rather than starting a duplicate. See docs/advanced-usage.md for a side-by-side comparison.
kubeport config init # Create a starter config (YAML or TOML)
kubeport config show # Display current config in a table
kubeport config validate # Validate config file
kubeport config set context my-cluster
kubeport config add --name "Redis" --pod redis-0 --local-port 6379 --remote-port 6379
kubeport config remove "Redis"
kubeport config path # Print resolved config file path| Capability | kubectl | Telepresence | Kubeport |
|---|---|---|---|
| Auto-reconnect | ✓ | ✓ | |
| Health checks | ✓ | ||
| Config file | ✓ | ||
| Multi-port discovery | ✓ | ||
| Lifecycle hooks | ✓ | ✓ | |
| Dynamic add/remove | ✓ | ||
| Background daemon | ✓ | ✓ | |
| Zero cluster footprint | ✓ | ✓ | |
| SOCKS5 / HTTP proxy | ✓ |
# Homebrew
brew install rbaliyan/tap/kubeport
# Install script
curl -sSfL https://raw.githubusercontent.com/rbaliyan/kubeport/main/install.sh | sh
# Go
go install github.com/rbaliyan/kubeport@latestPre-built binaries for Linux and macOS (amd64/arm64) are available on the releases page. Shell completions for bash, zsh, and fish are included. See the installation guide for all options.
| Guide | Description |
|---|---|
| Installation | All installation methods, requirements, and shell completions |
| Configuration | Config file format, service definitions, supervisor tuning |
| CLI Reference | Every command and flag |
| Proxy Servers | SOCKS5 and HTTP proxy for Kubernetes DNS translation |
| Lifecycle Hooks | Shell, exec, and webhook hooks with real-world examples |
| Architecture | How kubeport works under the hood |
| Advanced Usage | CI/CD integration, multiple clusters, remote control |
| Troubleshooting | Common issues, RBAC errors, and debugging tips |
| Shell Completions | Tab completion for bash, zsh, and fish |
| Client Library (SDK) | Use kubeport address translation in your Go application |
Example config files: YAML | TOML | Changelog
Contributions are welcome! See CONTRIBUTING.md for development setup, coding standards, and PR guidelines.
Quick start for contributors:
mise install # Install Go, linters, protoc, etc.
just build # Build the binary
just check # Format + lint + testSee the justfile for all available recipes.
Kubeport requires a Kubernetes cluster for end-to-end testing. We recommend kind (Kubernetes in Docker) for local development:
# Create a test cluster
kind create cluster --name kubeport-dev
# Deploy sample services
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels: { app: nginx }
template:
metadata:
labels: { app: nginx }
spec:
containers:
- name: nginx
image: nginx:alpine
ports: [{ containerPort: 80 }]
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector: { app: nginx }
ports: [{ port: 80 }]
EOF
# Test kubeport
just build
./bin/kubeport start --no-config --context kind-kubeport-dev \
--svc "nginx:svc/nginx:80:8080"
./bin/kubeport status
# Clean up
kind delete cluster --name kubeport-devminikube also works — set --context minikube instead. Unit tests (just test) do not require a cluster.
