█████╗ ██╗ ██╗████████╗ ██████╗ ████████╗██╗ ██╗███╗ ██╗███╗ ██╗███████╗██╗
██╔══██╗██║ ██║╚══██╔══╝██╔═══██╗╚══██╔══╝██║ ██║████╗ ██║████╗ ██║██╔════╝██║
███████║██║ ██║ ██║ ██║ ██║ ██║ ██║ ██║██╔██╗ ██║██╔██╗ ██║█████╗ ██║
██╔══██║██║ ██║ ██║ ██║ ██║ ██║ ██║ ██║██║╚██╗██║██║╚██╗██║██╔══╝ ██║
██║ ██║╚██████╔╝ ██║ ╚██████╔╝ ██║ ╚██████╔╝██║ ╚████║██║ ╚████║███████╗███████╗
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═══╝╚══════╝╚══════╝
A lightweight, auto and on-demand (lazy) port-forwarding proxy to Kubernetes. Tunnels are created lazily when traffic arrives to the local port and torn down after an idle timeout. Currently supports Kubernetes services and pods.
Connect to Kubernetes services and pods with friendly URLs automatically:
http://nginx-80.svc.default.ns.microk8s.cx.k8s.localhost:8989— Dynamic routing (no config needed)https://argocd.localhost:8989— Pre-configured route
- On-demand tunneling - port-forwards are created automatically when traffic arrives
- HTTP and HTTPS support - HTTP reverse proxy with
X-Forwarded-*headers, TLS passthrough for HTTPS - 💥 TCP port forwarding 🆕 - on-demand forward non-HTTP protocols (databases, Redis, etc.) to K8s services/pods
- 💥 Jump-host routing 🆕 - reach VPC-internal services on-demand (RDS, Cloud SQL) through a jump pod
- Auto-create jump pods 🆕 - automatically create jump pods on-demand
- K8s Services and Pods - target either directly or let it discover pods via service selectors
- Protocol multiplexing - serve both HTTP and HTTPS on a single port
- Idle cleanup - tunnels automatically close after configurable idle timeout
- Native client-go - uses Kubernetes client library directly (no kubectl subprocess)
brew install atas/tap/autotunnel
brew services start autotunnel # Start and enable auto-start on login
# Or run manually
# autotunnelAfter starting it, edit the created config file ~/.autotunnel.yaml with an editor to add your custom routes / kubeconfig paths.
The config auto-reloads on save unless port changes, then brew services restart autotunnel is required.
tail -f $(brew --prefix)/var/log/autotunnel.logOther useful commands
brew services stop autotunnel # Stop the service
brew services info autotunnel # Show information about the service
brew services restart autotunnel # Restart the service
brew services uninstall autotunnel # Uninstall the service
brew services list # Check statusLinux (systemd)
# Copy the service file (included in release archives)
mkdir -p ~/.config/systemd/user
cp autotunnel.service ~/.config/systemd/user/
# Enable and start
systemctl --user daemon-reload
systemctl --user enable --now autotunnel
# Check status and logs
systemctl --user status autotunnel
journalctl --user -u autotunnel -fGo Install
go install github.com/atas/autotunnel@latestDownload the latest release from the releases page.
- Run autotunnel, either manually or as a service. It will generate a default config file:
brew services start autotunnel #or just run `autotunnel` manually
# Creates ~/.autotunnel.yaml with example configuration-
Edit
~/.autotunnel.yamlwith your services: -
It will auto-reload unless port changes.
apiVersion: autotunnel/v1
# Verbose logging (can also use --verbose flag with higher priority)
# verbose: false
# Auto-reload on file changes. Any changes need `brew services restart autotunnel` while it is false.
auto_reload_config: true
# Common paths (/usr/local/bin, /opt/homebrew/bin, etc.) are added automatically.
# Add custom paths here if your credential plugin is in a non-standard location.
# exec_path:
# - /custom/path/to/binaries
http:
# Listen address - handles both HTTP and HTTPS (TLS passthrough) on same port
listen: "127.0.0.1:8989" # Port changes require: brew services restart autotunnel
# Idle timeout before closing tunnels (Go duration format)
# After this duration of no traffic, the tunnel will be closed
idle_timeout: 60m
k8s:
# Path(s) to kubeconfig. Supports colon-separated paths like $KUBECONFIG.
# Defaults to ~/.kube/config. Run `echo $KUBECONFIG` to see your value.
# kubeconfig: ~/.kube/config:~/.kube/prod-config
# Dynamic routing: access any K8s service or pod without pre-configuring routes
# Formats:
# Service: {service}-{port}.svc.{namespace}.ns.{context}.cx.{dynamic_host}
# Pod: {pod}-{port}.pod.{namespace}.ns.{context}.cx.{dynamic_host}
# Examples:
# http://nginx-80.svc.default.ns.my-cluster-context.cx.k8s.localhost:8989
# https://argocd-server-443.svc.argocd.ns.my-cluster-context.cx.k8s.localhost:8989
# http://nginx-2fxac-80.pod.default.ns.my-cluster-context.cx.k8s.localhost:8989
dynamic_host: k8s.localhost
routes:
# Static routes (take priority over dynamic routing)
# https://argocd.localhost:8989 (also supports http, http://argocd.localhost:8989)
argocd.localhost:
context: my-cluster-context # Kubernetes context name from kubeconfig
namespace: argocd # Kubernetes namespace
service: argocd-server # Kubernetes service name
port: 443 # Service port (automatically resolves to container targetPort)
scheme: https # Optional. Default is http.
# You can access remote https via local http too, not the other way around.
# TCP tunneling for non-HTTP protocols (databases, caches, etc.)
# For this section, we need an available and unique local port for each mapping.
tcp:
k8s:
routes:
# # PostgreSQL: connect via localhost:5432
# 5432: # local port
# context: my-cluster-context
# namespace: databases
# service: postgresql
# port: 5432 # remote port
# Jump-host routes via kubectl exec + socat/nc
# Use this to connect to VPC-internal services (RDS, Cloud SQL, etc.)
# through a jump pod that has network access to those services.
# ! NEED socat or nc (netcat) installed in the jump pod.
jump:
# # AWS RDS via existing pod (discovered from service)
# 3306: # local port
# context: my-cluster-context
# namespace: default
# via:
# service: backend-api # Use pod from this service
# target:
# host: mydb.rds.amazonaws.com
# port: 3306
# # Redis via auto-created jump pod
# 6380: # local port
# context: my-cluster-context
# namespace: default
# via:
# pod: autotunnel-jump
# create: # Optional, auto-create if there is no pod with socat/nc you can use
# image: alpine/socat:latest
# # command: ["sleep", "infinity"] # Optional: custom idle command (default: ["sleep", "infinity"])
# target:
# host: my-redis.cache.amazonaws.com
# port: 6379- Run autotunnel:
autotunnel- Access your services:
# HTTP/HTTPS routes
curl -k https://argocd.localhost:8989/
curl http://grafana.localhost:8989/
# TCP routes (if configured)
psql -h localhost -p 5432 -U postgres
redis-cli -p 6379flowchart LR
Client[HTTP Request<br/>Host: app.local:8989] --> autotunnel
subgraph autotunnel[autotunnel:8989]
direction TB
A[1. Extract Host] --> B[2. Lookup route]
B --> C[3. Create tunnel]
C --> D[4. Reverse proxy]
end
autotunnel -->|port-forward<br/>client-go| Pod[Kubernetes Pod]
- User configures hostname → K8s service/pod mappings in YAML config
- autotunnel listens on a single local port (e.g., 8989)
- When a request arrives, it inspects the
Hostheader (HTTP) or SNI (TLS) - If no tunnel exists for that host, it creates a port-forward using client-go
- It reverse-proxies the request through the tunnel
- After an idle timeout (no traffic), it closes the tunnel
Configuration file location: ~/.autotunnel.yaml (or specify with --config)
apiVersion: autotunnel/v1
# verbose: true # Enable verbose logging (or use --verbose flag)
# Auto-reload on file changes (disable requires: brew services restart autotunnel)
auto_reload_config: true
# Additional paths for exec credential plugins (e.g., aws-iam-authenticator, gcloud)
# Common paths (/usr/local/bin, /opt/homebrew/bin, etc.) are added automatically.
# exec_path:
# - /custom/path/to/binaries
http:
# Listen address (handles both HTTP and HTTPS on same port)
listen: "127.0.0.1:8989" # Port changes require: brew services restart autotunnel
# Idle timeout before closing tunnels (Go duration format)
idle_timeout: 60m
k8s:
# Path(s) to kubeconfig. Supports colon-separated paths like $KUBECONFIG.
# Defaults to ~/.kube/config. Run `echo $KUBECONFIG` to see your value.
# kubeconfig: ~/.kube/config:~/.kube/prod-config
routes:
# Route via service (discovers pod automatically)
argocd.localhost:
context: microk8s # Kubernetes context from kubeconfig
namespace: argocd # Kubernetes namespace
service: argocd-server # Kubernetes service name
port: 443 # Service port
scheme: https # Sets X-Forwarded-Proto header
grafana.localhost:
context: microk8s
namespace: observability
service: grafana
port: 3000
# Route directly to a pod (no pod discovery)
debug.localhost:
context: microk8s
namespace: default
pod: my-debug-pod # Pod name (use instead of service)
port: 8080
# TCP tunneling for non-HTTP protocols (databases, caches, etc.)
# Each route listens on a local port and forwards to a K8s service/pod
tcp:
idle_timeout: 60m # Optional, defaults to http.idle_timeout
k8s:
# kubeconfig: ~/.kube/config # Optional, same format as http.k8s.kubeconfig
# Direct port-forward routes (native K8s port-forward)
routes:
# PostgreSQL: connect via localhost:5432
5432:
context: microk8s
namespace: databases
service: postgresql
port: 5432
# Redis: connect via localhost:6379
6379:
context: microk8s
namespace: caching
service: redis-master
port: 6379
# Direct pod targeting (no service discovery)
27017:
context: microk8s
namespace: databases
pod: mongodb-0
port: 27017
# Jump-host routes for VPC-internal services (RDS, Cloud SQL, etc.)
# Connects through a jump pod that has network access to the target.
# ! NEED socat or nc (netcat) installed in the jump pod.
jump:
# AWS RDS MySQL via existing jump pod
3306:
context: eks-prod
namespace: default
via:
service: backend-api # Discover pod from service selector
# pod: bastion-pod # OR use direct pod instead of service
# container: main # Optional: for multi-container pods
target:
host: mydb.cluster-xyz.us-east-1.rds.amazonaws.com
port: 3306
# Cloud SQL via auto-created jump pod
5433:
context: gke-prod
namespace: default
via:
pod: autotunnel-jump # Pod name to create & re-use
create:
image: alpine/socat:latest # Auto-create pod with this image
# command: ["sleep", "infinity"] # Optional: custom idle command (default: ["sleep", "infinity"])
# timeout: 60s # Optional: pod readiness timeout (default: 60s)
target:
host: 10.0.0.5 # VPC-internal IP
port: 5432
method: socat # Optional: "socat" (default), nc is automatic fallbackEach route requires either service or pod (mutually exclusive):
| Field | Description |
|---|---|
context |
Kubernetes context name from kubeconfig |
namespace |
Kubernetes namespace |
service |
Service name (autotunnel discovers a ready pod) |
pod |
Pod name (direct targeting, no discovery) |
port |
Service or pod port |
scheme |
http (default) or https - sets X-Forwarded-Proto header |
Direct port-forward to K8s services/pods. Each route requires either service or pod:
| Field | Description |
|---|---|
context |
Kubernetes context name from kubeconfig |
namespace |
Kubernetes namespace |
service |
Service name (discovers a ready pod) |
pod |
Pod name (direct targeting, no discovery) |
port |
Target port on the service/pod |
Usage:
psql -h localhost -p 5432 -U postgres
redis-cli -p 6379Connect to VPC-internal services through a jump pod. Requires socat or nc in the jump pod.
| Field | Description |
|---|---|
context |
Kubernetes context name |
namespace |
Kubernetes namespace |
via.service |
Service to discover jump pod from (mutually exclusive with via.pod) |
via.pod |
Direct jump pod name (mutually exclusive with via.service) |
via.container |
Container name (optional, for multi-container pods) |
via.create.image |
Image for auto-creating jump pod (requires via.pod) |
via.create.timeout |
Pod readiness timeout (default: 60s) |
target.host |
Target hostname or IP (e.g., RDS endpoint) |
target.port |
Target port |
method |
socat (default) - forwarding method in jump pod (nc is auto-fallback) |
Auto-created pods have labels app.kubernetes.io/managed-by: autotunnel. Clean up with:
kubectl delete pod -l app.kubernetes.io/managed-by=autotunnelUsage: autotunnel [options]
Options:
-config string
Path to configuration file (default "~/.autotunnel.yaml")
-verbose
Enable verbose logging
-version
Show version information
On most systems, *.localhost resolves to 127.0.0.1 automatically. This makes it easy to use autotunnel without modifying /etc/hosts:
http:
k8s:
routes:
myapp.localhost:
# ...Then access via: http://myapp.localhost:8989/
For custom hostnames, add entries to /etc/hosts:
127.0.0.1 myapp.local
127.0.0.1 api.local
autotunnel uses standard Kubernetes port-forwarding. Access is governed by your kubeconfig credentials and RBAC policies. Use appropriate caution when connecting to production environments.
go build -o autotunnel .go test -v ./...Releases are automated via GoReleaser. Push a tag to trigger:
git tag v0.1.0
git push origin v0.1.0MIT License - see LICENSE for details.