A command-line tool to manage kubectl port-forwards to Kubernetes services. Uses kubectl v1.22 to work around the SSL/TLS connection reset bug introduced in kubectl v1.23+.
kubectl v1.23+ has a regression (kubernetes/kubernetes#103526) that causes port-forward to terminate when PostgreSQL (and other databases) send TCP RST packets during normal SSL operations. kubectl v1.22 and earlier auto-reconnect on errors, effectively working around this issue.
This tool uses kubectl v1.22 for port-forwarding to provide reliable database connections without the need for workarounds like disabling SSL or using SSH bastion pods.
- Reliable Port-Forwarding: Uses kubectl v1.22 to avoid the v1.23+ SSL bug
- Direct Service Access: No SSH bastion or NodePort required
- State Management: Tracks active tunnels with metadata (service, ports, PIDs)
- Smart Completion: Bash completion for services, ports, and tunnel identifiers
- Random Ports: Optionally use random local ports to avoid conflicts
- Easy Management: List and kill tunnels by service name, port, or PID
- Bash 4.0+
- kubectl v1.22.0 (see installation instructions below)
python3(for random port generation)- Standard Unix tools:
grep,cut,awk,sort,tr,ss - Access to a Kubernetes cluster
# 1. Download and install kubectl v1.22
curl -LO "https://dl.k8s.io/release/v1.22.0/bin/linux/amd64/kubectl"
chmod +x kubectl
mkdir -p ~/.local/bin
mv kubectl ~/.local/bin/kubectl-1.22
# 2. Copy the main script
cp k8s-proxy ~/.local/bin/k8s-proxy
chmod +x ~/.local/bin/k8s-proxy
# 3. Copy the completion script
mkdir -p ~/.local/share/bash-completion/completions
cp k8s-proxy-completion.bash ~/.local/share/bash-completion/completions/k8s-proxy
# 4. Add to your ~/.bashrc (if not already present)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
# Source completion for user scripts
cat >> ~/.bashrc << 'EOF'
# Load user completions
if [ -d "$HOME/.local/share/bash-completion/completions" ]; then
for completion in "$HOME/.local/share/bash-completion/completions"/*; do
[ -r "$completion" ] && . "$completion"
done
fi
EOF
# 5. Reload your shell
source ~/.bashrc-
Install kubectl v1.22:
mkdir -p ~/.local/bin curl -LO "https://dl.k8s.io/release/v1.22.0/bin/linux/amd64/kubectl" chmod +x kubectl mv kubectl ~/.local/bin/kubectl-1.22
-
Install the main script:
cp k8s-proxy ~/.local/bin/ chmod +x ~/.local/bin/k8s-proxy
-
Install bash completion:
mkdir -p ~/.local/share/bash-completion/completions cp k8s-proxy-completion.bash ~/.local/share/bash-completion/completions/k8s-proxy
-
Update your PATH (add to
~/.bashrcif not present):export PATH="$HOME/.local/bin:$PATH"
-
Enable user completions (add to
~/.bashrc):if [ -d "$HOME/.local/share/bash-completion/completions" ]; then for completion in "$HOME/.local/share/bash-completion/completions"/*; do [ -r "$completion" ] && . "$completion" done fi
-
Reload your shell:
source ~/.bashrc
The script uses the following defaults that can be modified in the script itself:
- KUBECTL:
~/.local/bin/kubectl-1.22- Path to kubectl v1.22 - STATE_DIR:
~/.local/share/k8s-proxy- Directory for tunnel state files - CACHE_TTL: 300 seconds (5 minutes) - How long to cache Kubernetes service list
# Same port as remote (default)
k8s-proxy start <service.namespace> <port>
# Custom local port
k8s-proxy start <service.namespace> <remote-port> <local-port>
# Random local port
k8s-proxy start <service.namespace> <remote-port> randomExamples:
# Connect to service1 postgres on same port locally
k8s-proxy start service1-postgres-rw.service1 5432
# Connect to service2 postgres on different local port
k8s-proxy start service2-postgres-rw.service2 5432 15432
# Connect to postgres on random local port
k8s-proxy start service1-postgres-rw.service1 5432 randomk8s-proxy listOutput:
Active k8s-proxy tunnels:
----------------------------
PID: 1234567
Service: service1-postgres-rw.service1
Local: localhost:5432
Remote: service1-postgres-rw.service1:5432
# By service name
k8s-proxy kill service1-postgres-rw.service1
# By local port
k8s-proxy kill 5432
# By PID
k8s-proxy kill 1234567The completion system provides intelligent suggestions:
-
Command completion:
k8s-proxy <TAB>→ showsstart,list,kill -
Service completion:
k8s-proxy start <TAB>→ shows Kubernetes services- Format:
service-name.namespace - First use may be empty (cache is being built in background)
- Wait 2-3 seconds and try again
- Cache is refreshed every 5 minutes
- Format:
-
Port completion:
k8s-proxy start service1-postgres-rw.service1 <TAB>- Queries Kubernetes service for actual exposed ports
- Shows real ports like
5432instead of generic suggestions - Falls back to common database ports if query fails
-
Local port completion: After entering remote port, suggests:
- Same port as remote
randomfor random port selection
-
Kill completion:
k8s-proxy kill <TAB>- Shows service names of active tunnels only
-
Port-Forward Creation: Uses kubectl v1.22 with
kubectl port-forward -n <namespace> service/<service> <local>:<remote> -
Auto-Reconnect: kubectl v1.22 automatically reconnects when connections are reset (unlike v1.23+)
-
State Tracking: Saves tunnel metadata to
~/.local/share/k8s-proxy/<pid>.tunnel- Service name (service.namespace)
- Local and remote ports
- Namespace
- Creation timestamp
-
Cleanup: Automatically removes stale tunnel files when processes are no longer running
-
Service Discovery: Queries Kubernetes API for all services across all namespaces for completion
kubectl v1.23.0 introduced a regression that breaks port-forwarding to databases:
- The Bug: PR #103526 made kubectl immediately terminate on ANY error
- Impact: PostgreSQL's SSL connection close (TCP RST) is treated as fatal
- Affected: All CloudNativePG databases (SSL enabled by default)
- Status: Unfixed as of 2025 (kubernetes/kubectl#1169)
kubectl v1.22 and earlier don't have this issue - they auto-reconnect on errors, providing a stable tunnel.
Tunnel state is stored in ~/.local/share/k8s-proxy/:
*.tunnel- Active tunnel metadata files (named by PID)services.cache- Cached list of Kubernetes services (refreshed every 5 minutes)
# Reload completion
source ~/.local/share/bash-completion/completions/k8s-proxy
# Or reload entire shell
source ~/.bashrcThe first time you use completion, it triggers a background cache build. Wait 2-3 seconds and try again.
# Verify kubectl-1.22 is installed
kubectl-1.22 version --client
# If not, download it
cd ~/.local/bin
curl -LO "https://dl.k8s.io/release/v1.22.0/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl kubectl-1.22Either:
- Use a different local port:
k8s-proxy start <service.namespace> <port> <different-port> - Use random port:
k8s-proxy start <service.namespace> <port> random - Kill the existing tunnel:
k8s-proxy kill <port>
Check if you're using the right kubectl version:
# k8s-proxy should show this
k8s-proxy start service1-postgres-rw.service1 5432
# Output should say: "Using kubectl v1.22 (workaround for v1.23+ SSL bug)"# Start tunnel
k8s-proxy start service1-postgres-rw.service1 5432
# Use with psql
PGPASSWORD=<password> psql -h localhost -p 5432 -U postgres -d service1
# Use with DataGrip
# Host: localhost
# Port: 5432
# Database: service1
# User: postgres
# Kill when done
k8s-proxy kill service1-postgres-rw.service1# Different services on same port (use different local ports)
k8s-proxy start service1-postgres-rw.service1 5432 5432
k8s-proxy start service2-postgres-rw.service2 5432 5433
# List all
k8s-proxy list
# Kill all by service name
k8s-proxy kill service1-postgres-rw.service1
k8s-proxy kill service2-postgres-rw.service2# Start tunnel
k8s-proxy start service1-postgres-rw.service1 5432
# Configure DataGrip:
# - Host: localhost
# - Port: 5432
# - Database: service1
# - User: postgres
# - Password: <from kubernetes secret>
# Keep tunnel running in background
# DataGrip will automatically reconnect if connection drops- kubectl v1.23+ bug: This tool exists because of this bug - use k8s-proxy instead of kubectl directly
- First connection may show error: kubectl v1.22 auto-reconnects, so first attempt may fail but subsequent ones succeed
- Requires cluster access: Your kubeconfig must have valid credentials
- kubernetes/kubernetes#103526 - The regression that broke port-forward
- kubernetes/kubectl#1169 - Port-forward drops after first connection
- kubernetes/kubernetes#111825 - PostgreSQL connection resets
- kubernetes/kubectl#1620 - Cancelled TCP request bug
# Remove scripts
rm ~/.local/bin/k8s-proxy
rm ~/.local/bin/kubectl-1.22
rm ~/.local/share/bash-completion/completions/k8s-proxy
# Remove state files
rm -rf ~/.local/share/k8s-proxy
# Remove shell configuration (manual - edit ~/.bashrc)
# Remove the PATH and completion lines added during installationFree to use and modify.