Documentation for Jetty

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:

  1. Creates a tunnel in Bridge via the API
  2. Starts the edge agent to establish the WebSocket connection
  3. Updates the CR status with the public URL and connection state
  4. 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+
  • kubectl configured 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

Send feedback

Found an issue or have a suggestion? Let us know.