AWS Deployment (ECS Fargate)
This guide deploys a full Bridge cluster on AWS using ECS Fargate (a service that runs containers without you managing servers). One CloudFormation template creates everything: the database, networking, load balancer, and the containers that make up the cluster.
What CloudFormation does for you. CloudFormation reads a template file and builds all the AWS resources it describes, in the right order. You run one command; AWS does the rest. To remove everything later, you delete the one “stack” it created.
Overview
The template builds a production-shaped cluster:
- An orchestrator that runs migrations, stays in charge, and keeps workers running.
- Proxy containers that accept connections and serve the web app.
- Session containers that the orchestrator spawns and replaces on demand.
- An Amazon RDS PostgreSQL database (the Bridge’s storage).
- Amazon EFS shared file storage for recordings (so any container can read any recording).
- An internal Network Load Balancer (NLB) by default, exposing the web app over HTTPS and the native protocol ports only to the private networks you allow.
You don’t manage the proxy and session containers directly - the orchestrator creates, replaces, and load-balances them for you.
Architecture
This diagram follows the resources and parameters in
bridge-cluster-aws.cfn.yaml. The NLB can be internal or internet-facing, but
the ECS workers always run with private IPs only.
flowchart LR
Clients["Approved clients<br/>(browser + native tools)"]
Britive["Britive platform"]
Targets["Target networks<br/>(VPC, peering, TGW, VPN, Direct Connect, NAT)"]
subgraph LbSubnets["Load balancer subnets<br/>LoadBalancerSubnetIds"]
NLB["Network Load Balancer<br/>443 / 2222 / 3389 / 3306"]
end
subgraph WorkerSubnets["Private worker/data subnets<br/>SubnetIds"]
Proxy["Proxy workers<br/>ECS Fargate, private IPs"]
Session["Session workers<br/>ECS Fargate, private IPs"]
RDS[("RDS PostgreSQL<br/>checkout + session state")]
EFS[("EFS<br/>recordings")]
end
subgraph OrchestratorSubnets["Private orchestrator/broker subnets<br/>BrokerPrivateSubnetIds"]
Orchestrator["Orchestrator + Britive Broker<br/>ECS Fargate, private IP"]
end
Clients -->|"HTTPS / native protocols"| NLB
NLB -->|"target groups"| Proxy
Proxy -->|"session control + relay"| Session
Session -->|"protocol connection"| Targets
Proxy --> RDS
Session --> RDS
Orchestrator --> RDS
Proxy --> EFS
Session --> EFS
Orchestrator -->|"spawn / replace workers"| Proxy
Orchestrator -->|"spawn / replace workers"| Session
Orchestrator <-->|"outbound TLS"| Britive
Orchestrator -->|"Bridge API via NLB 443"| NLB
Private is the default and recommended deployment model. For nearly all real deployments, keep Bridge behind an internal/private NLB reachable only from your corporate network, VPN, zero-trust network, or private connectivity into AWS. Some native protocols can carry credentials or session data in clear text depending on the client and target configuration, so exposing native listeners on an internet-facing load balancer increases the chance of credential exposure.
The template can still create an internet-facing NLB by setting
LoadBalancerScheme=internet-facing and restricting ClientCidr to a narrow,
reviewed client range. Use that mode only when it is an explicit requirement. In
that model, restrict the deployment to browser-only access over HTTPS and do not
expose native cleartext protocol listeners publicly.
Before You Begin
You need:
- An AWS account and the AWS CLI installed and signed in
(
aws sts get-caller-identityshould show your account). - Permission to create IAM roles, ECS, RDS, EFS, and load balancers
(the deploy uses
CAPABILITY_NAMED_IAM). - An existing VPC with three private, NAT-routed subnets for workers, database, and recording storage.
- Network routes from those private worker subnets to every target Bridge should reach. That can be same-VPC routing, VPC peering, Transit Gateway, VPN, Direct Connect, or NAT egress for internet-reachable targets. Target security groups and firewalls must also allow traffic from the Bridge worker security group or worker subnet CIDRs.
- Subnets for the NLB. Use private subnets for the default internal NLB. Use
public subnets only when you intentionally set
LoadBalancerScheme=internet-facing; the workers still run with private IPs only. - An ACM certificate for the hostname you’ll use (for example
bridge.example.com), in the same region. The load balancer presents this for HTTPS. - The CloudFormation template for the Bridge cluster, downloaded here: bridge-cluster-aws.cfn.yaml. Save it locally - you’ll deploy it in Step 2.
You don’t build anything. The Bridge image is published on Docker Hub as
britive/bridge and the AWS configuration is baked into it; the template selects
it automatically. You set per-deployment values through CloudFormation parameters
(no config file to mount).
Step 1: Choose the Image
The Bridge image lives on Docker Hub at britive/bridge. Use a specific
version tag (for example v2.0.0 - use the latest release version):
britive/bridge:latestECS Fargate can pull this public image directly, so in most cases there’s
nothing to do here - the template already defaults ImageUri to
britive/bridge:latest.
Optional - mirror to your own ECR. Some organizations prefer to pull from a
private registry (for Docker Hub rate limits or network policy). If so, copy the
image into your ECR and pass that URI as ImageUri:
REGION=us-west-2
ACCOUNT=111122223333
ECR=$ACCOUNT.dkr.ecr.$REGION.amazonaws.com/bridge
aws ecr create-repository --repository-name bridge --region us-west-2 || true
aws ecr get-login-password --region us-west-2 \
| docker login --username AWS --password-stdin $ACCOUNT.dkr.ecr.$REGION.amazonaws.com
docker pull britive/bridge:latest
docker tag britive/bridge:latest $ECR:latest
docker push $ECR:latestStep 2: Prepare the Secrets
The cluster needs a few shared secrets. Generate them once and keep them safe - you’ll pass the same values on every future update.
echo "EncryptionKeyB64: $(openssl rand -base64 32)" # encrypts stored credentials
echo "ClusterToken: $(openssl rand -hex 32)" # secures internal cluster calls
echo "HostKeySeed: $(openssl rand -hex 16)" # stable SSH fingerprint across workers
echo "DbPassword: $(openssl rand -hex 16)" # RDS database passwordThe HostKeySeed makes every session worker present the same SSH host key, so users’ SSH clients don’t warn about a changed fingerprint each time a worker is replaced. Keep it constant.
Step 3: Deploy the CloudFormation Stack
Run the deploy command, filling in your own values. This single command creates the entire cluster:
Run this from the folder where you saved bridge-cluster-aws.cfn.yaml, filling in your own values. This single command creates the entire cluster:
aws cloudformation deploy \
--template-file bridge-cluster-aws.cfn.yaml \
--stack-name bridge \
--region us-west-2 \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides \
VpcId="vpc-xxxxxxxx" \
SubnetIds="subnet-private-a,subnet-private-b,subnet-private-c" \
LoadBalancerSubnetIds="subnet-private-a,subnet-private-b,subnet-private-c" \
LoadBalancerScheme="internal" \
CertificateArn="arn:aws:acm:us-west-2:111122223333:certificate/xxxx" \
ClientCidr="10.20.0.0/16" \
BridgeApiUrl="https://bridge.example.com" \
DbPassword="<DbPassword from Step 2>" \
ClusterToken="<ClusterToken from Step 2>" \
EncryptionKeyB64="<EncryptionKeyB64 from Step 2>" \
HostKeySeed="<HostKeySeed from Step 2>"The template defaults ImageUri to britive/bridge:latest (Docker Hub), so you
don’t pass it unless you mirrored the image to your own ECR - then add
ImageUri="<your-ecr-uri>:latest" to the overrides above.
LoadBalancerScheme defaults to internal, so you may omit it from the command
if you are using the recommended private deployment. To create an internet-facing
load balancer, set LoadBalancerScheme="internet-facing", set
LoadBalancerSubnetIds to public subnets, and keep SubnetIds as private,
NAT-routed worker/data subnets. Keep ClientCidr limited to the smallest
reviewed client range.
Parameter Reference
| Parameter | Required | Description |
|---|---|---|
ImageUri | No | Defaults to britive/bridge:latest. Override only to use your own mirrored registry. |
VpcId | Yes | The VPC to deploy into. |
SubnetIds | Yes | Three private, NAT-routed subnets for workers, database, and file storage. Workers always use private IPs only. These subnets must have routes to the target servers Bridge will connect to, and targets must allow traffic from the worker security group or subnet CIDRs. |
LoadBalancerSubnetIds | Yes | Subnets for the NLB. Use private subnets for the default internal NLB; use public subnets only with LoadBalancerScheme=internet-facing. |
LoadBalancerScheme | No | Defaults to internal. Set to internet-facing only for an explicitly reviewed public endpoint, and pair it with a narrow ClientCidr. |
CertificateArn | Yes | ACM certificate for the HTTPS (443) listener on the load balancer. |
ClientCidr | Yes | The only network range allowed to reach the load balancer. For internal NLBs, use your routed private client or VPN range. For internet-facing NLBs, use a narrow office/VPN egress CIDR; do not use a broad internet range for native protocol listeners. |
BridgeApiUrl | Yes | The public URL users reach the Bridge at (matches your certificate’s hostname). |
DbPassword | Yes | Master password for the RDS PostgreSQL database. |
ClusterToken | Yes | Shared secret for internal calls between cluster containers. |
EncryptionKeyB64 | Yes | Base64 32-byte key that encrypts stored credentials. Never change after go-live. |
HostKeySeed | Yes | Seed that gives session workers a stable SSH host key. |
BrokerAuthToken | No | Token for the optional Britive Broker (leave empty to disable). |
BrokerTenantSubdomain | No | Tenant subdomain for the Broker. |
BrokerPrivateSubnetIds | No | Private subnets for the Broker task. |
NatEgressCidr | No | NAT gateway address allowed to call the Bridge API (for the Broker). |
SshProvisionKeyPem | No | SSH key the Broker uses to provision Linux targets. |
The deploy takes several minutes. CloudFormation prints progress and finishes
with Successfully created/updated stack.
Step 4: Point DNS at the Load Balancer
Get the load balancer’s DNS name from the stack outputs:
aws cloudformation describe-stacks --stack-name bridge --region us-west-2 \
--query 'Stacks[0].Outputs' --output tableCreate a DNS record (a CNAME, or an Alias record in Route 53) for your hostname
(bridge.example.com) pointing at that load balancer DNS name. For the default
internal NLB, publish this record in private DNS or another DNS zone resolvable
only from your approved networks. Your certificate’s hostname must match.
Verify
1. Check the cluster is running. All containers should be on your new image:
aws ecs list-tasks --cluster bridge --region us-west-2You should see an orchestrator, one or more proxies, and one or more session
workers, all RUNNING.
2. Confirm the orchestrator started cleanly and the license loaded by tailing its logs:
aws logs tail /bridge/v2 --follow --region us-west-2Look for log lines showing the database connected, migrations applied, and the
license status (operational=true) if you’ve installed one.
3. Open the web app at your hostname (https://bridge.example.com/) and log
in.
Updating the Cluster Later
To move to a newer Bridge release, re-run the same deploy command with the new
version in ImageUri (for example ImageUri="britive/bridge:v2.1.0", or your
mirrored ECR equivalent). Pass every other parameter unchanged.
The orchestrator handles the rollout for you: it cycles the proxy containers onto the new image and replaces idle session workers, with no manual container restarts. See Operations for more on upgrades.
If you use the lower-level update-stack command instead of deploy, pass
UsePreviousValue=true for every secret parameter so you don’t have to re-enter
them.
Troubleshoot
| Symptom | Likely Cause | Fix |
|---|---|---|
| Stack fails to create | Missing IAM capability, or a parameter (subnet, cert) is wrong | Read the failure reason in the CloudFormation events; confirm the VPC, subnets, and certificate exist in the same region. |
| Containers crash on start | Can’t reach RDS, or a secret is malformed | Check /bridge/v2 logs; confirm the database came up and EncryptionKeyB64 is valid base64. |
| Sessions fail to connect to targets | Worker subnets cannot route to the target, or target firewalls block the worker source | Confirm the private worker subnets have a route to the target network and the target allows inbound traffic from TaskSg or the worker subnet CIDRs. |
| Can’t reach the web app | ClientCidr doesn’t include your address, private routing is missing, or DNS not set | Confirm your client or VPN network is within ClientCidr; verify private DNS and routing to the internal load balancer. |
| Footer shows “Unlicensed” / native protocols unavailable | No valid license installed (running in limited mode) | Install a license in Admin → License, or bake one into the config - see Licensing. |
| Old version still serving | ImageUri unchanged, or a moving tag was cached | Use a specific version tag (e.g. britive/bridge:v2.1.0) in ImageUri and redeploy. |
Next Steps
- Configure authentication
- Install and manage your license.
- Review protocols, recording, operations for the Admin console, upgrades, and backups.