Port forwarding made easy. A Kubernetes port forwarding tool that automatically maintains persistent port forwards to pods with automatic reconnection and failure recovery.
- 🔄 Automatic Port Forwarding: Maintains persistent port forwards to Kubernetes pods
- 🔁 Automatic Reconnection: Automatically reconnects on pod restarts or connection failures
- ⚙️ Easy Configuration: CUE-based configuration for simple pod forwarding setup
- 📊 Structured Logging: Comprehensive logging with configurable levels and pretty-printing
- 🔐 Kubernetes Integration: Seamless integration with your Kubernetes cluster (local or in-cluster)
- 🚀 Multiple Forwards: Support for multiple simultaneous port forwards
- Go 1.25.1 or later - Download Go
- Access to a Kubernetes cluster (local kubeconfig or in-cluster)
Verify Go is installed:
go versionInstall fwkeeper directly:
go install github.com/codozor/fwkeeper@latestThis installs the binary to $GOPATH/bin (typically ~/go/bin), which should already be in your PATH.
Verify installation:
fwkeeper --version
fwkeeper --helpIf fwkeeper is not found, ensure $GOPATH/bin is in your PATH:
# Add to ~/.bashrc, ~/.zshrc, or ~/.bash_profile
export PATH="$HOME/go/bin:$PATH"For development or building locally:
git clone https://github.com/codozor/fwkeeper.git
cd fwkeeper
go build -o fwkeeper ./
./fwkeeper run -c fwkeeper.cueCreate a fwkeeper.cue file:
logs: {
level: "debug"
pretty: true
}
forwards: [
{
name: "postgres"
ports: ["5432"]
namespace: "default"
resource: "postgres-pod"
},
{
name: "api"
ports: ["8080:8080", "9000:9000"]
namespace: "api"
resource: "api-server-deployment-abc123"
}
]./fwkeeper run -c fwkeeper.cueOr using the default fwkeeper.cue:
./fwkeeper runThe port forwards are now active. Connect to your services:
# Connect to postgres
psql -h localhost -p 5432
# Connect to API
curl http://localhost:8080fwkeeper uses CUE for configuration validation and schema enforcement.
logs: { ... }
forwards: [ ... ]logs: {
level: "debug" # "error", "warn", "info", "debug", "trace" (default: "info")
pretty: true # Pretty-print logs to console (default: false)
}forwards: [
{
name: "service-name" # Unique identifier for this forward
ports: ["8080", "9000:3000"] # Local:remote port mappings
namespace: "default" # Kubernetes namespace
resource: "pod-name" # Pod, Service, Deployment, StatefulSet, or DaemonSet
},
# ... more forwards
]Resource Reference Syntax:
"pod-name"- Direct pod reference"svc/service-name"or"service/service-name"- Kubernetes Service"dep/deployment-name"or"deployment/deployment-name"- Kubernetes Deployment"sts/statefulset-name"or"statefulset/statefulset-name"- Kubernetes StatefulSet"ds/daemonset-name"or"daemonset/daemonset-name"- Kubernetes DaemonSet
When using a Service, Deployment, StatefulSet, or DaemonSet, fwkeeper automatically finds and connects to the first running pod that matches the resource's selector.
Port Mapping Syntax:
"8080"- Forward local port 8080 to pod port 8080"8080:9000"- Forward local port 8080 to pod port 9000
Validation Rules:
- Port numbers must be between 1 and 65535
- Each forward must have a unique name
- Namespace and resource must be specified
- Local ports must be unique across all forwards
# Override kubeconfig location
KUBECONFIG=/path/to/kubeconfig ./fwkeeper run
# Use default kubeconfig locations
# 1. $KUBECONFIG environment variable
# 2. ~/.kube/config
# 3. In-cluster config (if running inside Kubernetes)# Show help
./fwkeeper --help
# Run with default config (fwkeeper.cue)
./fwkeeper run
# Run with custom config file
./fwkeeper run -c /path/to/config.cue
# Show command help
./fwkeeper run --helpPress Ctrl+C to gracefully stop fwkeeper. It will:
- Cancel all active port forwards
- Close connections
- Print shutdown message
fwkeeper automatically detects changes to your configuration file and reloads without interrupting active port forwards (when possible).
Automatic Reload:
- fwkeeper watches your config file for changes
- When the file is saved, the configuration is automatically reloaded
- New forwards are started, removed forwards are stopped, modified forwards are restarted
Manual Reload: Send a SIGHUP signal to trigger manual reload:
# In another terminal, while fwkeeper is running
kill -HUP <pid>
# Or using pkill
pkill -HUP fwkeeperConfig Reload Behavior:
- Invalid config → Current configuration continues, error logged, reload skipped
- New forwards → Started automatically
- Removed forwards → Stopped gracefully
- Modified forwards → Restarted with new configuration
- Unchanged forwards → Continue running without interruption
Example:
- Start fwkeeper:
./fwkeeper run -c fwkeeper.cue - Edit
fwkeeper.cue(add, remove, or modify forwards) - Save the file
- fwkeeper detects the change and updates automatically
- View the logs to see what changed
Forward to a PostgreSQL database pod:
logs: {
level: "info"
pretty: true
}
forwards: [
{
name: "database"
ports: ["5432"]
namespace: "databases"
resource: "postgres-primary"
}
]Then connect:
psql -h localhost -p 5432 -U myuser -d mydbForward multiple services for local development:
logs: {
level: "debug"
pretty: true
}
forwards: [
{
name: "api-server"
ports: ["8000:8000"]
namespace: "development"
resource: "api-server"
},
{
name: "frontend"
ports: ["3000:3000"]
namespace: "development"
resource: "frontend"
},
{
name: "postgres"
ports: ["5432:5432"]
namespace: "databases"
resource: "postgres-dev"
},
{
name: "redis"
ports: ["6379:6379"]
namespace: "databases"
resource: "redis-dev"
}
]Forward multiple ports from a single pod:
forwards: [
{
name: "api-services"
ports: ["8000:8000", "9000:9000", "5000:5000"]
namespace: "api"
resource: "api-pod"
}
]Forward to pods managed by Deployments, StatefulSets, or DaemonSets:
logs: {
level: "info"
pretty: true
}
forwards: [
{
name: "api-deployment"
ports: ["8080:8080"]
namespace: "production"
resource: "dep/api-server" # Deployment: automatically finds a running pod
},
{
name: "postgres-statefulset"
ports: ["5432:5432"]
namespace: "databases"
resource: "sts/postgres-primary" # StatefulSet: automatically finds a running pod
},
{
name: "monitoring-daemonset"
ports: ["9090:9090"]
namespace: "monitoring"
resource: "ds/prometheus" # DaemonSet: automatically finds a running pod
}
]The resource finder automatically locates the first running pod managed by the specified Deployment, StatefulSet, or DaemonSet. This is useful when you have multiple replicas and want to connect to any available pod.
- error: Only errors
- warn: Warnings and errors
- info: General information (default)
- debug: Detailed debugging information
- trace: Very detailed tracing information
Logs are written to stderr. Each log entry includes:
- Timestamp (Unix milliseconds)
- Log level
- Message
- Error details (when applicable)
Enable pretty-printed logs for development:
logs: {
pretty: true
}Output will include timestamps and color-coded levels for better readability.
The pod exists but isn't currently running. Check pod status:
kubectl get pods -n <namespace> <pod-name>
kubectl describe pod -n <namespace> <pod-name>The pod restarted or the port-forward connection dropped. fwkeeper will automatically reconnect with exponential backoff (starting at 100ms, up to 30s). Check logs for details.
Verify your kubeconfig:
# Check KUBECONFIG env var
echo $KUBECONFIG
# Test cluster access
kubectl cluster-info
# Set correct kubeconfig
export KUBECONFIG=~/.kube/config
./fwkeeper runVerify your CUE configuration syntax and against the schema:
# Check for CUE syntax errors in your config file
# Ensure all fields match the required structure in schema.cueCommon issues:
- Missing required fields (name, ports, namespace, resource)
- Invalid port numbers (not 1-65535)
- Malformed port specifications (should be "port" or "local:remote")
Enable debug logging to see detailed information:
logs: {
level: "debug"
pretty: true
}Run all tests in the project:
go test ./...Run tests with verbose output:
go test -v ./...Run specific test package:
go test -v ./internal/configRun specific test:
go test -v -run TestPortValidation ./internal/configRun tests with coverage:
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.outPhase 1 (Completed):
- ✅ Config loading and parsing (valid config, parsing errors)
- ✅ Port validation (range: 1-65535, invalid ports, port conflicts)
- ✅ Port conflict detection (duplicate local ports across forwards)
Phase 2 (Planned):
- Locator implementations (Pod, Service, Deployment, StatefulSet, DaemonSet)
- Forward name uniqueness validation
- Configuration hot-reload
Phase 3 (Planned):
- Runner lifecycle (startup, shutdown, error handling)
- Forwarder reconnection behavior
fwkeeper/
├── cmd/ # CLI command definitions
│ ├── root.go # Root command
│ └── run.go # Run command
├── internal/
│ ├── app/ # Application orchestration
│ │ └── runner.go # Main runner and lifecycle management
│ ├── bootstrap/ # Dependency injection setup
│ ├── config/ # Configuration loading and validation
│ │ └── schema.cue # CUE schema definition
│ ├── forwarder/ # Port forwarding logic
│ │ └── forwarder.go # Individual pod port forwarder
│ ├── kubernetes/ # Kubernetes client setup
│ ├── locator/ # Pod discovery and location
│ │ └── locator.go # Pod/service locator implementations
│ └── logger/ # Logging setup
├── main.go # Application entry point
├── go.mod # Go module definition
├── go.sum # Dependency checksums
├── fwkeeper.cue # Default configuration
└── README.md # This file
# Build the binary
go build -o fwkeeper ./
# Build with version info
go build -ldflags="-X main.version=v1.0.0" -o fwkeeper ./# Run all tests
go test ./...
# Run with verbose output
go test -v ./...
# Run specific test
go test -run TestName ./path/to/package# Format code
go fmt ./...
# Run static analysis
go vet ./...
# Run golangci-lint (if installed)
golangci-lint run ./...Key dependencies:
- Cobra: CLI framework
- CUE: Configuration language
- Zerolog: Structured logging
- samber/do: Dependency injection
- client-go: Kubernetes client library
- Runner: Orchestrates port forwarders, manages context and graceful shutdown
- Forwarder: Implements individual pod port forwarding with automatic reconnection
- Config: CUE-based configuration parsing and validation
- Logger: Structured logging with zerolog
- Kubernetes Integration: Handles kubeconfig loading and client initialization
- Read configuration from CUE file
- Load Kubernetes credentials
- For each forward:
- Locate the pod
- Verify pod is running
- Establish SPDY connection to pod
- Forward ports
- Reconnect on failure (exponential backoff: 100ms → 30s with jitter)
- Listen for interrupt signal (Ctrl+C)
- Gracefully shutdown all forwarders
This project is under MIT License
For issues, questions, or contributions, please visit the repository.
