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:
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:
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
kubectlconfigured to reach it (kubectl get nodesshould 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.
docker pull britive/bridge:latest
docker tag britive/bridge:latest myregistry.example.com/bridge:latest
docker push myregistry.example.com/bridge:latestStep 2: Generate the Secrets
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 workersStep 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:
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):
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
kubectl apply -f bridge-cluster-k8s.yamlThis 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):
kubectl logs -f -n bridge deployment/bridge-orchestrator2. Watch the session and proxy pods appear:
kubectl get pods -n bridge -L bridge.roleAfter 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:
kubectl get svc -n bridge bridge-proxyIf your cluster provisions load balancers, use the external IP. Otherwise, port-forward for a quick test:
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: falsein 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 (setBRIDGE_DATASTORE_SSLMODEtorequire). - 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
| Symptom | Likely Cause | Fix |
|---|---|---|
| Orchestrator pod crash-loops | Can’t reach PostgreSQL, or config invalid | kubectl logs -n bridge deployment/bridge-orchestrator; check the Secret’s datastore values and the ConfigMap YAML. |
| No session/proxy pods appear | Orchestrator lacks permission to create pods, or the driver image/namespace is wrong | Confirm the ServiceAccount + Role/RoleBinding are applied; check the provisioner.k8s.image and namespace. |
| Settings seem ignored (wrong worker count) | ConfigMap uses old option names | Update to the current names (see the warning in Step 3). |
| Recordings missing after a pod is replaced | Node-local storage on a multi-node cluster | Switch to a ReadWriteMany shared volume. |
| Can’t reach the web app | No load balancer controller, or wrong Service type | Use port-forward to test, or add an Ingress/LoadBalancer appropriate to your platform. |
Next Steps
- Configure authentication
- Install and manage your license.
- Review protocols, recording, operations for the Admin console, upgrades, and backups.