kubectl port-forward: Reach a Pod From the Runner
kubectl port-forward opens a local TCP port that tunnels through the API server to a pod or service port.
Integration tests on a runner often need to hit an in-cluster service. port-forward gives the job a localhost endpoint without exposing anything externally.
What it does
kubectl port-forward maps localPort:podPort and forwards traffic through the Kubernetes API server to the target pod (or a pod behind a service/deployment). It runs in the foreground until interrupted, so in CI you background it and wait for the port.
Common usage
kubectl port-forward pod/api 8080:8080
kubectl port-forward svc/api 8080:80 -n staging
# CI pattern: background, wait, run, clean up
kubectl port-forward svc/api 8080:80 >/dev/null 2>&1 &
PF=$!
until nc -z localhost 8080; do sleep 1; done
curl -fsS localhost:8080/health
kill $PFOptions
| Flag / Syntax | What it does |
|---|---|
| <local>:<remote> | Map a local port to the target port |
| :<remote> | Let kubectl choose a random free local port |
| svc/<name> | pod/<name> | Forward to a service or a specific pod |
| --address | Bind address(es), default 127.0.0.1 |
| -n, --namespace | Namespace of the target |
In CI
Background the process and store its PID so the job can kill it at the end; a leaked forward keeps the step alive. Wait for the port with nc -z or a curl retry loop rather than a fixed sleep. Forwarding to a Service load-balances to one backing pod, which is usually fine for a health check.
Common errors in CI
"error: unable to listen on any of the requested ports" means the local port is already in use; pick another or use :remote for a random one. "an error occurred forwarding ... connection refused" means the pod is not listening on that port yet; wait for readiness first. "lost connection to pod" appears if the pod restarts mid-forward; re-establish the forward.
Using this in CI
A runner has no kubeconfig, no cached context, and no interactive auth. Every kubectl invocation in CI needs the context supplied explicitly, and most confusing CI failures here are the command running against the wrong cluster or no cluster at all.
# never rely on the ambient context on a runner
kubectl --context "$KUBE_CONTEXT" -n "$NAMESPACE" get pods
# confirm what you are actually connected to before mutating anything
kubectl config current-context
kubectl cluster-info
# fail fast instead of hanging on an unreachable API server
kubectl --request-timeout=30s get nodes