Clusters · Desktop · Pro and Team

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.

Where
Desktop
Plans
Pro and Team
Role
The account’s, from 2.7.0 (see Team and permissions)

What it does

ModeForHow it works
Cluster serviceA 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 networkA host the cluster reaches outside it: RDS, Azure Database, a VM, a managed databaseA 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?

ToolWhereWhere the connection startsUse it when
Pod shellWeb and desktopInside an app containerYou need to get into a running container.
Node shellWeb (admins) and desktopA privileged pod on the node itselfThe problem is the node: kubelet, disk, host network.
BastionWeb onlyA temporary unprivileged pod in the namespaceYou want to test or reach, from the browser, a host only the cluster can reach (ssh, curl, nc).
Secure tunnelDesktop onlyYour machine, through the clusterYou 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

LimitValue
Tunnels per mode, per clusterup to 5
Relay pod1 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 imagethe same as the Bastion: docker.io/nicolaka/netshoot:v0.16@sha256:b09d9b21381f47a79b3cbcb30da25266dc17186ea00ae65e99fdc51396f48e70
Local port127.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.