Kubernetes Operator
Deploy Jetty tunnels as Kubernetes custom resources. The operator watches for JettyTunnel CRs and manages the tunnel lifecycle automatically -- creating tunnels via the Bridge API, running the edge agent, and cleaning up on deletion.
Overview
The Jetty Kubernetes Operator lets you declare tunnels as YAML manifests in your cluster. When you apply a JettyTunnel resource, the operator:
- Creates a tunnel in Bridge via the API
- Starts the edge agent to establish the WebSocket connection
- Updates the CR status with the public URL and connection state
- Deletes the tunnel when the CR is removed
This is useful for:
- Exposing staging services in Kubernetes to external collaborators
- Creating tunnels as part of CI/CD pipelines (deploy + tunnel in one step)
- Managing tunnels declaratively alongside your application manifests
- Temporary tunnels in preview environments that clean up with the namespace
Installation
Prerequisites
- Kubernetes 1.24+
kubectlconfigured for your cluster- A Jetty API token (create one in Bridge under Tokens)
Install with Helm
helm repo add jetty https://charts.usejetty.online
helm install jetty-edge jetty/jetty-edge \
--namespace jetty-system \
--create-namespace \
--set jetty.apiToken=YOUR_API_TOKEN \
--set jetty.edgeSecret=YOUR_EDGE_SECRET
Install from source
# Apply the CRD
kubectl apply -f deploy/kubernetes/crds/jetty-tunnel.yaml
# Apply RBAC
kubectl apply -f deploy/kubernetes/operator/rbac.yaml
# Build and deploy the operator
docker build -f deploy/kubernetes/operator/Dockerfile -t jetty-operator:latest deploy/kubernetes/operator/
kubectl apply -f deploy/kubernetes/operator/deployment.yaml
Using an existing secret
Store your credentials in a Kubernetes secret instead of passing them as Helm values:
kubectl create secret generic jetty-credentials \
--namespace jetty-system \
--from-literal=api-token=YOUR_API_TOKEN \
--from-literal=edge-secret=YOUR_EDGE_SECRET
Then install with:
helm install jetty-edge jetty/jetty-edge \
--namespace jetty-system \
--set jetty.existingSecret=jetty-credentials
Creating tunnels
Basic HTTP tunnel
apiVersion: jetty.dev/v1alpha1
kind: JettyTunnel
metadata:
name: my-api
spec:
service: my-api-service
port: 8080
Apply it:
kubectl apply -f tunnel.yaml
Check status:
kubectl get jettytunnels
# or shorthand:
kubectl get jt
Output:
NAME SERVICE PORT PROTOCOL STATUS URL AGE
my-api my-api-service 8080 http Live https://my-api.tunnels.usejetty.online 2m
TCP tunnel
apiVersion: jetty.dev/v1alpha1
kind: JettyTunnel
metadata:
name: db-tunnel
spec:
service: postgres
port: 5432
protocol: tcp
With a reserved subdomain
apiVersion: jetty.dev/v1alpha1
kind: JettyTunnel
metadata:
name: staging
spec:
service: frontend
port: 3000
subdomain: staging-preview
With routing rules
Route different paths to different services:
apiVersion: jetty.dev/v1alpha1
kind: JettyTunnel
metadata:
name: microservices
spec:
service: frontend
port: 3000
subdomain: staging
routingRules:
- matchType: path_prefix
pathPrefix: /api
localHost: api-service
localPort: 8080
- matchType: path_prefix
pathPrefix: /admin
localHost: admin-service
localPort: 3001
With auto-expiry
Tunnels that clean up after a set duration:
apiVersion: jetty.dev/v1alpha1
kind: JettyTunnel
metadata:
name: demo
spec:
service: demo-app
port: 3000
expiresIn: "8h"
With basic auth
Protect the tunnel with a username and password stored in a Kubernetes secret:
kubectl create secret generic tunnel-auth \
--from-literal=username=admin \
--from-literal=password=secret
apiVersion: jetty.dev/v1alpha1
kind: JettyTunnel
metadata:
name: protected
spec:
service: my-app
port: 3000
basicAuth:
secretName: tunnel-auth
Tunnel status
The operator updates the CR status subresource with tunnel state:
kubectl describe jettytunnel my-api
Status fields:
| Field | Description |
|---|---|
phase |
Current state: Pending, Connected, Live, Degraded, Failed |
publicUrl |
Public HTTPS URL (HTTP tunnels) |
publicEndpoint |
Public endpoint including protocol and port (TCP/UDP tunnels) |
tunnelId |
Bridge tunnel ID |
assignedPort |
Public port for TCP/UDP tunnels |
lastHeartbeat |
Timestamp of last successful heartbeat |
message |
Human-readable status message or error |
Cleanup
Delete a tunnel:
kubectl delete jettytunnel my-api
The operator calls the Bridge API to delete the tunnel. The public URL stops working immediately.
When a namespace is deleted, all JettyTunnel resources in it are cleaned up automatically.
Helm values reference
| Value | Default | Description |
|---|---|---|
replicaCount |
1 | Edge server replicas |
image.repository |
ghcr.io/shaferllc/jetty-edge |
Edge server image |
image.tag |
latest |
Edge server image tag |
jetty.apiUrl |
https://usejetty.online |
Bridge API URL |
jetty.apiToken |
"" |
API token (use existingSecret for production) |
jetty.existingSecret |
"" |
Kubernetes secret name for credentials |
jetty.edgeSecret |
"" |
Shared secret with Bridge |
jetty.tunnelHost |
tunnels.usejetty.online |
Tunnel hostname |
jetty.metricsEnabled |
false |
Enable Prometheus metrics |
operator.enabled |
true |
Deploy the operator alongside edge |
ingress.enabled |
false |
Create an Ingress resource |
resources |
100m/128Mi - 500m/256Mi | Resource limits |
Related guides
- TCP/UDP tunnels -- raw protocol support
- Routing rules -- path and header-based routing
- Tunnel settings -- basic auth, expiry, rate limits
- API reference -- Bridge API documentation
Send feedback
Found an issue or have a suggestion? Let us know.