Secure tunnel: from localhost to the database, through the cluster
On desktop, you work from your own machine: Kubepier opens a secure tunnel through the Kubernetes cluster to the database (a port-forward to the Service, or a relay to RDS and other hosts on the network) and a local terminal (bash or zsh on Linux and macOS, PowerShell on Windows) listing each localhost:<port> → target. There is no terminal in a pod and no key field: you use your own clients and keys.
What it does
| Mode | For | How it works |
|---|---|---|
| Cluster service | A Kubernetes Service (a database, an API, a panel running in the cluster) | Direct port-forward to the Service. No pod is created. |
| Host on the cluster network | A host the cluster reaches outside it: RDS, Azure Database, a VM, a managed database | A minimal restricted relay pod (socat only, no interactive access) carries up to 5 tunnels and is deleted when the last tunnel closes, at 2 hours or when the app quits. |
In either mode, the local port only listens on 127.0.0.1.
Which one to use?
| Tool | Where | Where the connection starts | Use it when |
|---|---|---|---|
| Pod shell | Web and desktop | Inside an app container | You need to get into a running container. |
| Node shell | Web (admins) and desktop | A privileged pod on the node itself | The problem is the node: kubelet, disk, host network. |
| Bastion | Web only | A temporary unprivileged pod in the namespace | You want to test or reach, from the browser, a host only the cluster can reach (ssh, curl, nc). |
| Secure tunnel | Desktop only | Your machine, through the cluster | You want to use the clients on your machine (psql, DBeaver, browser, ssh) against a cluster service or a host in its network. |
Step by step: prerequisites
Pro or Team plan. Your kubeconfig needs the permissions of the mode you use. Cluster service mode:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: kubepier-tunel-servico
namespace: <namespace>
rules:
- apiGroups: [""]
resources: ["services", "pods"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods/portforward"]
verbs: ["create", "get"] Host on the cluster network mode (also creates and deletes the relay pod):
Role for the Host on the cluster network mode
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: kubepier-tunel-relay
namespace: <namespace>
rules:
- apiGroups: [""]
resources: ["services", "pods"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["create", "delete"]
- apiGroups: [""]
resources: ["pods/exec", "pods/portforward"]
verbs: ["create", "get"] list on pods is optional in Host mode: without it, Kubepier just skips cleaning up your leftover relays. Bind each Role to your identity with a RoleBinding in the same namespace. The cluster network must reach the target host.
Step by step: open a tunnel
- In the cluster menu, or the + of the bottom panel, pick Secure tunnel.
- Pick the mode. Cluster service: enter the Namespace, pick the Service and the Port. Host on the cluster network: enter the Namespace the relay will run in, the Target host and the Port, as the cluster sees them.
- Under Local port, enter a port or leave it automatic.
- Click Open tunnel. In Host mode, the relay shows as Starting and then Ready (up to 120 seconds).
- The Tunnels panel lists localhost:<port> → target, with Copy and, for well-known ports, Copy command (psql, mysql, redis-cli, ssh -p, sqlcmd, mongosh or the browser URL).
- Open local terminal opens a terminal on your machine (bash or zsh on Linux and macOS, PowerShell on Windows) with the Active secure tunnels: header and the KUBEPIER_TUNNEL_1, KUBEPIER_TUNNEL_2… variables holding 127.0.0.1:<port> for each tunnel. The cluster’s local terminals get the same.
Example: port-forward to PostgreSQL on RDS or Azure
Host on the cluster network mode, target meu-rds.xxxx.rds.amazonaws.com:5432, local port 15432:
# Linux and macOS (bash/zsh) and Windows (PowerShell): same command
psql "host=127.0.0.1 port=15432 user=app dbname=app sslmode=require" In DBeaver: Host 127.0.0.1, Port 15432, Database app, User app; under SSL, require mode. The server certificate carries the RDS or Azure name, not 127.0.0.1, so verify-full fails hostname verification: use require, or set the expected server name if the client allows it.
Example: MySQL
Target on port 3306, local port 13306:
mysql -h 127.0.0.1 -P 13306 -u app -p app Use 127.0.0.1, not localhost: with localhost, the mysql client tries the local socket. On Windows, the same command works in PowerShell with mysql.exe on the PATH.
Example: Redis
Target on port 6379 (6380 with TLS on Azure Cache), local port 16379:
redis-cli -h 127.0.0.1 -p 16379 --tls
# server without TLS:
redis-cli -h 127.0.0.1 -p 16379 With TLS, the certificate name does not match 127.0.0.1 either; if redis-cli refuses, use --sni with the server name (or --insecure just for the test).
Example: SSH to an internal VM
Host on the cluster network mode, target 10.0.1.20:22, local port 2222:
# Linux and macOS
ssh -p 2222 usuario@127.0.0.1
scp -P 2222 usuario@127.0.0.1:/var/log/app.log .
rsync -e "ssh -p 2222" -av usuario@127.0.0.1:/srv/app/ ./app/
# Windows (PowerShell, Windows OpenSSH)
ssh -p 2222 usuario@127.0.0.1
scp -P 2222 usuario@127.0.0.1:/var/log/app.log . known_hosts records the VM as [127.0.0.1]:2222. rsync does not ship with Windows: use scp, or WSL.
Example: SQL Server and MongoDB
# SQL Server (local port 11433)
sqlcmd -S 127.0.0.1,11433 -U app
# MongoDB (local port 27018); directConnection stops the driver from trying the other replica set members
mongosh "mongodb://app@127.0.0.1:27018/app?directConnection=true" Example: internal HTTP admin UI
Target on port 80 or 8080, local port 8080. Open http://localhost:8080 in the browser.
# Linux and macOS
curl -H "Host: painel.interno" http://localhost:8080/
# Windows (PowerShell)
Invoke-WebRequest -Uri http://localhost:8080/ -Headers @{ Host = "painel.interno" } If the service answers by virtual host (Host header), it may refuse localhost. Test with the header above, or point painel.interno to 127.0.0.1 in the hosts file and open http://painel.interno:8080.
Limits and timeouts
| Limit | Value |
|---|---|
| Tunnels per mode, per cluster | up to 5 |
| Relay pod | 1 per cluster per person; waits up to 120 s to be ready; deleted when the last tunnel closes, at 2 hours or when the app quits |
| Relay image | the same as the Bastion: docker.io/nicolaka/netshoot:v0.16@sha256:b09d9b21381f47a79b3cbcb30da25266dc17186ea00ae65e99fdc51396f48e70 |
| Local port | 127.0.0.1 only |
Audit
- Every tunnel opened and closed (tunel_abrir, tunel_fechar) and every relay pod created and removed (relay_criar, relay_remover) go to kubepier-audit.log, with who, when, cluster, namespace and target.
- Never the traffic going through the tunnel.
Common errors
- Connection refused: wrong host or port, or an NSG/security group blocks the cluster node subnet. Test the target with nc -vz from the web Bastion.
- Local port in use: pick another local port.
- The relay image was not pulled: the nodes cannot reach Docker Hub; mirror docker.io/nicolaka/netshoot:v0.16@sha256:b09d9b21381f47a79b3cbcb30da25266dc17186ea00ae65e99fdc51396f48e70 into the company registry.
- You already have an active relay: close it or wait for it to expire; one per cluster.
- Tunnel limit: at most 5 per mode in this cluster.
- PodSecurity or admission refused the relay: it is already restricted; check the cluster’s own policies.
- 403: a verb from the chosen mode’s Role is missing.