Skip to content
AWS Deployment (ECS Fargate)

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-identity should 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):

image
britive/bridge:latest

ECS 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:

terminal
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:latest

Step 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.

terminal
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 password

The 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:

terminal
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

ParameterRequiredDescription
ImageUriNoDefaults to britive/bridge:latest. Override only to use your own mirrored registry.
VpcIdYesThe VPC to deploy into.
SubnetIdsYesThree 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.
LoadBalancerSubnetIdsYesSubnets for the NLB. Use private subnets for the default internal NLB; use public subnets only with LoadBalancerScheme=internet-facing.
LoadBalancerSchemeNoDefaults to internal. Set to internet-facing only for an explicitly reviewed public endpoint, and pair it with a narrow ClientCidr.
CertificateArnYesACM certificate for the HTTPS (443) listener on the load balancer.
ClientCidrYesThe 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.
BridgeApiUrlYesThe public URL users reach the Bridge at (matches your certificate’s hostname).
DbPasswordYesMaster password for the RDS PostgreSQL database.
ClusterTokenYesShared secret for internal calls between cluster containers.
EncryptionKeyB64YesBase64 32-byte key that encrypts stored credentials. Never change after go-live.
HostKeySeedYesSeed that gives session workers a stable SSH host key.
BrokerAuthTokenNoToken for the optional Britive Broker (leave empty to disable).
BrokerTenantSubdomainNoTenant subdomain for the Broker.
BrokerPrivateSubnetIdsNoPrivate subnets for the Broker task.
NatEgressCidrNoNAT gateway address allowed to call the Bridge API (for the Broker).
SshProvisionKeyPemNoSSH 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:

terminal
aws cloudformation describe-stacks --stack-name bridge --region us-west-2 \
  --query 'Stacks[0].Outputs' --output table

Create 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:

terminal
aws ecs list-tasks --cluster bridge --region us-west-2

You 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:

terminal
aws logs tail /bridge/v2 --follow --region us-west-2

Look 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

SymptomLikely CauseFix
Stack fails to createMissing IAM capability, or a parameter (subnet, cert) is wrongRead the failure reason in the CloudFormation events; confirm the VPC, subnets, and certificate exist in the same region.
Containers crash on startCan’t reach RDS, or a secret is malformedCheck /bridge/v2 logs; confirm the database came up and EncryptionKeyB64 is valid base64.
Sessions fail to connect to targetsWorker subnets cannot route to the target, or target firewalls block the worker sourceConfirm 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 appClientCidr doesn’t include your address, private routing is missing, or DNS not setConfirm your client or VPN network is within ClientCidr; verify private DNS and routing to the internal load balancer.
Footer shows “Unlicensed” / native protocols unavailableNo valid license installed (running in limited mode)Install a license in Admin → License, or bake one into the config - see Licensing.
Old version still servingImageUri unchanged, or a moving tag was cachedUse a specific version tag (e.g. britive/bridge:v2.1.0) in ImageUri and redeploy.

Next Steps

Last updated on