Skip to content
Kubernetes Deployment

Kubernetes Deployment

This guide deploys a full Bridge cluster on Kubernetes. It uses the same orchestrator / proxy / session roles as the AWS deployment, but the orchestrator creates workers as Kubernetes pods instead of ECS tasks. It works on managed Kubernetes (EKS, GKE, AKS) and on self-managed clusters.

What is Kubernetes? Kubernetes runs and manages containers across a group of machines. You describe what you want in YAML “manifests” and apply them with kubectl; Kubernetes makes it so. If you already run apps on Kubernetes, the Bridge fits right in.

Overview

You’ll create resources in a dedicated bridge namespace:

  • A PostgreSQL database (a pod for testing, or point at a managed database for production).
  • A ConfigMap holding the Bridge configuration.
  • A Secret holding the encryption key and other secrets.
  • The orchestrator Deployment, with a ServiceAccount and permission to manage pods.
  • A Service that exposes the proxy pods to users.

The orchestrator creates the proxy and session pods itself - you don’t define them as static Deployments.

Architecture

This diagram follows the Helm chart and the downloadable bridge-cluster-k8s.yaml manifest. Kubernetes provides the scheduling, Service routing, and RBAC; Bridge’s orchestrator owns the proxy and session worker lifecycle.

    flowchart LR
  Clients["Approved clients<br/>(browser + native tools)"]
  Britive["Britive platform<br/>(optional broker connection)"]
  Targets["Target networks<br/>(services, nodes, databases, devices)"]

  subgraph Cluster["Kubernetes cluster"]
    subgraph Namespace["bridge namespace"]
      ProxySvc["bridge-proxy Service<br/>LoadBalancer / NodePort / L4 edge"]
      K8sApi["Kubernetes API<br/>pods create/watch/delete"]

      subgraph Control["Control plane resources"]
        Config["ConfigMap<br/>Bridge config"]
        Secrets["Secret<br/>cluster token + encryption key"]
        RBAC["ServiceAccount + RoleBinding"]
      end

      Orchestrator["Orchestrator pod<br/>migrations + reconciler<br/>optional co-located broker"]
      ProxyPods["Proxy pods<br/>spawned by orchestrator"]
      SessionPods["Session pods<br/>spawned by orchestrator"]
      Postgres[("PostgreSQL<br/>managed DB or bundled pod")]
      Recordings[("Shared recordings volume<br/>RWX PVC / EFS / Azure Files / NFS")]
    end
  end

  Clients -->|"HTTPS / native protocols"| ProxySvc
  ProxySvc -->|"ready proxy endpoints"| ProxyPods
  ProxyPods -->|"session control + relay"| SessionPods
  SessionPods -->|"protocol connection"| Targets

  Orchestrator -->|"uses RBAC"| K8sApi
  K8sApi -->|"creates / retires"| ProxyPods
  K8sApi -->|"creates / retires"| SessionPods

  Config --> Orchestrator
  Secrets --> Orchestrator
  RBAC --> Orchestrator

  Orchestrator --> Postgres
  ProxyPods --> Postgres
  SessionPods --> Postgres
  ProxyPods --> Recordings
  SessionPods --> Recordings
  Orchestrator --> Recordings

  Orchestrator <-->|"outbound TLS when broker is enabled"| Britive
  

You can deploy with Helm, or use the downloadable Kubernetes manifest below if you want to inspect and tune each resource by hand. The only external runtime artifact you need is the public britive/bridge container image.

Install with Helm

If you use Helm or Rancher (whose Apps catalog is Helm-based), install the Bridge chart from Docker Hub as an OCI chart:

terminal
export BRIDGE_CLUSTER_TOKEN="$(openssl rand -hex 32)"
export BRIDGE_ENCRYPTION_KEY_B64="$(openssl rand -base64 32)"
export BRIDGE_HOST_KEY_SEED="$(openssl rand -hex 16)"

helm install bridge oci://registry-1.docker.io/britive/bridge-chart \
  --version 1.0.0 \
  -n bridge --create-namespace \
  --set secrets.clusterToken="$BRIDGE_CLUSTER_TOKEN" \
  --set secrets.encryptionKeyB64="$BRIDGE_ENCRYPTION_KEY_B64" \
  --set secrets.hostKeySeed="$BRIDGE_HOST_KEY_SEED" \
  --set config.auth.britive.tenant="your-tenant"

To enable the co-located broker and automatic online license retrieval, also set the broker-pool token:

terminal
helm upgrade --install bridge oci://registry-1.docker.io/britive/bridge-chart \
  --version 1.0.0 \
  -n bridge --create-namespace \
  --reuse-values \
  --set secrets.brokerAuthToken="<broker-pool-token>"

For production, point datastore.* at a managed PostgreSQL database, configure shared ReadWriteMany storage for recordings, pin a tested Bridge image tag, and front the proxy Service with an Ingress or cloud load balancer that terminates TLS. In Rancher, add the same OCI chart URL to Apps and set the equivalent values in the install form.

The rest of this guide walks the raw-manifest path, which is useful when you want to read or hand-tune every Kubernetes resource.

Before You Begin

You need:

  • A working Kubernetes cluster and kubectl configured to reach it (kubectl get nodes should list your nodes).
  • The starting manifest, downloaded here: bridge-cluster-k8s.yaml. You’ll customize it below.
  • For production: a way to provide shared storage for recordings that every pod can read and write (a ReadWriteMany volume such as EFS, Azure Files, or NFS), and ideally a managed PostgreSQL database.

You don’t build anything. The Bridge image is published on Docker Hub as britive/bridge and the manifest already references britive/bridge:latest. As long as your cluster’s nodes can reach Docker Hub, they pull it automatically.

Step 1: Make the Image Reachable

The manifest you downloaded uses britive/bridge:latest from Docker Hub. If your cluster nodes have internet access, there’s nothing to do - skip to Step 2.

Air-gapped or private-registry clusters: mirror the image into a registry your nodes can reach, then update the image: fields in the manifest to match.

terminal
docker pull britive/bridge:latest
docker tag britive/bridge:latest myregistry.example.com/bridge:latest
docker push myregistry.example.com/bridge:latest

Step 2: Generate the Secrets

terminal
echo "ENCRYPTION_KEY_B64: $(openssl rand -base64 32)"   # encrypts stored credentials
echo "CLUSTER_TOKEN:      $(openssl rand -hex 32)"        # secures internal cluster calls
echo "HOST_KEY_SEED:      $(openssl rand -hex 16)"        # stable SSH fingerprint across workers

Step 3: Customize the Manifest

Open the manifest you downloaded (bridge-cluster-k8s.yaml). It’s one file containing all the resources. Update these pieces:

The Secret - paste in the values from Step 2:

bridge-cluster.yaml
apiVersion: v1
kind: Secret
metadata:
  name: bridge-secrets
  namespace: bridge
stringData:
  BRIDGE_DATASTORE_HOST: "bridge-postgres"
  BRIDGE_DATASTORE_PORT: "5432"
  BRIDGE_DATASTORE_USER: "bridge"
  BRIDGE_DATASTORE_PASSWORD: "bridge"
  BRIDGE_DATASTORE_NAME: "bridge"
  BRIDGE_DATASTORE_SSLMODE: "disable"
  BRIDGE_ENCRYPTION_KEY_B64: "<ENCRYPTION_KEY_B64 from Step 2>"
  BRIDGE_CLUSTER_TOKEN: "<CLUSTER_TOKEN from Step 2>"
  BRIDGE_HOST_KEY_SEED: "<HOST_KEY_SEED from Step 2>"

The ConfigMap - this holds the Bridge configuration. Make sure the cluster settings use the current option names shown here, and that the orchestrator’s provisioner uses the public Bridge image (or your private-registry mirror):

bridge-cluster.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: bridge-config
  namespace: bridge
data:
  config.yaml: |
    cluster:
      session:
        max_concurrent_sessions: 1
        session_limit: 1          # one session, then the worker is replaced
        lifespan: "0s"            # no time-based replacement
      proxy:
        lifespan: "0s"            # "0s" = don't cycle proxies; e.g. "36h" in prod
        drain_deadline: "30s"
        max_cycle_concurrency: 1
      orchestrator:
        warm_session_workers: 3   # keep 3 session pods ready
        proxy_workers: 1          # keep 1 proxy pod running
        reconcile: "5s"           # how often to check and top up
        provisioner:
          platform: "k8s"
          path: "/opt/bridge/drivers/k8s/driver"
          k8s:
            image: "britive/bridge:latest"
            namespace: "bridge"
    server:
      listen: "8080"
      tls:
        enabled: false            # terminate TLS at an Ingress/load balancer
      recording:
        output_dir: /data/recordings
      auth:
        types: ["britive"]
        britive:
          tenant: "your-tenant"   # subdomain only, e.g. "acme" — see /reference/finding-your-tenant
    ssh:
      native:  { enabled: true, listen: "2222" }
      browser: { enabled: true }
      idle_timeout: "30m"

Use the current option names. Older copies of this manifest may use names like session_target, proxy_target, max_sessions, lifespan_minutes, or driver_path. Those are no longer read and will be silently ignored (falling back to defaults). The correct names are shown above: warm_session_workers, proxy_workers, session_limit, lifespan (a duration string), reconcile, and the provisioner block with platform/path/k8s.image/k8s.namespace.

The image references - the manifest already uses britive/bridge:latest everywhere. Only change the image: fields (orchestrator Deployment and the provisioner.k8s.image) if you mirrored the image to a private registry in Step 1.

Recordings storage. The starting manifest uses a node-local path for recordings, which only works on a single node. For a multi-node cluster, replace it with a ReadWriteMany volume (EFS, Azure Files, NFS, and similar) mounted at /data/recordings on the worker pods, so any pod can read any recording.

Step 4: Apply It

terminal
kubectl apply -f bridge-cluster-k8s.yaml

This creates the namespace, database, config, secret, RBAC, orchestrator, and the proxy Service.

Verify

1. Watch the orchestrator start (it runs migrations and then begins creating workers):

terminal
kubectl logs -f -n bridge deployment/bridge-orchestrator

2. Watch the session and proxy pods appear:

terminal
kubectl get pods -n bridge -L bridge.role

After a moment you should see bridge-orchestrator, one or more proxy pods, and several session pods (matching warm_session_workers).

3. Reach the web app. Find the proxy Service’s external address:

terminal
kubectl get svc -n bridge bridge-proxy

If your cluster provisions load balancers, use the external IP. Otherwise, port-forward for a quick test:

terminal
kubectl port-forward -n bridge svc/bridge-proxy 8080:8080
# then open http://localhost:8080/

Production Notes

  • TLS: Put an Ingress or cloud load balancer with a real certificate in front of the proxy Service, and keep tls.enabled: false in the config so TLS is terminated at the edge.
  • Database: Use a managed PostgreSQL service instead of the in-cluster PostgreSQL pod, and point the Secret’s BRIDGE_DATASTORE_* values at it (set BRIDGE_DATASTORE_SSLMODE to require).
  • Recordings: Use a ReadWriteMany persistent volume so recordings survive pod replacement and are readable cluster-wide.
  • Resources: Add CPU/memory requests and limits to the pods, sized for your session load (session pods do the heavy work).
  • Image pulls: Pin a tested Bridge image tag for production. If your cluster cannot pull from Docker Hub, mirror that same tag into a private registry and update both image references in the manifest.

Troubleshoot

SymptomLikely CauseFix
Orchestrator pod crash-loopsCan’t reach PostgreSQL, or config invalidkubectl logs -n bridge deployment/bridge-orchestrator; check the Secret’s datastore values and the ConfigMap YAML.
No session/proxy pods appearOrchestrator lacks permission to create pods, or the driver image/namespace is wrongConfirm the ServiceAccount + Role/RoleBinding are applied; check the provisioner.k8s.image and namespace.
Settings seem ignored (wrong worker count)ConfigMap uses old option namesUpdate to the current names (see the warning in Step 3).
Recordings missing after a pod is replacedNode-local storage on a multi-node clusterSwitch to a ReadWriteMany shared volume.
Can’t reach the web appNo load balancer controller, or wrong Service typeUse port-forward to test, or add an Ingress/LoadBalancer appropriate to your platform.

Next Steps

Last updated on