Overview
The Thunders Browserless image is a unified container that packages:
Browserless Enterprise - Headless browser engine supporting Chromium, Firefox, and WebKit
Nginx Reverse Proxy - Request routing, WebSocket proxying, health checks, and file serving
Azure Relay Bridge - Outbound-only connection to Azure Relay for secure communication with Thunders's platform
All three components run inside a single container managed by supervisord, making deployment straightforward.
How It Works
Key points:
No inbound firewall rules required - The relay bridge connects outbound to Azure Relay over HTTPS (port 443)
No VPN needed - Azure Relay handles secure tunneling
Single container - One image to pull, one container to run
Prerequisites
An Azure subscription (or any environment that can run Docker containers)
ACR credentials provided by Thunders (to pull the image)
Azure Relay configuration (see Azure Relay section below)
For Azure deployments: Azure CLI installed and authenticated
Credentials You Will Receive from Thunders
Thunders will provide the following credentials for your deployment:
Credential | Description |
ACR Server | Container registry URL (e.g., |
ACR Username | Token name for pulling the image |
ACR Password | Token password for pulling the image |
Relay Namespace | Azure Relay namespace (if Thunders-managed) |
Relay Hybrid Connection Name | Hybrid connection name (if Thunders-managed) |
Relay Listen Key | SAS key for the relay listener (if Thunders-managed) |
Store these securely. Never commit them to source control.
Azure Relay Configuration
Azure Relay enables secure communication between Thunders's platform and your self-hosted browserless instance without opening inbound firewall ports. There are two setup options:
Option A: Thunders-Managed Relay (Recommended)
Thunders creates and manages the Azure Relay namespace and hybrid connection in Thunders's Azure subscription. You will receive:
RELAY_NAMESPACE- The relay namespace URLRELAY_HYCO- The hybrid connection nameRELAY_LISTEN_KEY- The SAS key with listen permission
Your container connects outbound to this relay. Thunders's platform connects from the other side using send permissions.
No Azure Relay setup required on your end.
Option B: Customer-Managed Relay
If you prefer to manage the relay in your own Azure subscription:
Create an Azure Relay namespace:
az relay namespace create \
--resource-group \
--name \
--location
2. Create a hybrid connection:
az relay hyco create \
--resource-group \
--namespace-name \
--name
3. Create authorization rules:
Create a listen rule (for your self-hosted container):
az relay hyco authorization-rule create \
--resource-group \
--namespace-name \
--hybrid-connection-name \
--name listen \
--rights Listen
Create a send rule (for Thunders's platform):
az relay hyco authorization-rule create \
--resource-group \
--namespace-name \
--hybrid-connection-name \
--name send \
--rights Send
4. Retrieve the keys:
Get the listen key (for your container):
az relay hyco authorization-rule keys list \
--resource-group \
--namespace-name \
--hybrid-connection-name \
--name listen \
--query primaryKey -o tsv
Get the send key (share this with Thunders):
az relay hyco authorization-rule keys list \
--resource-group \
--namespace-name \
--hybrid-connection-name \
--name send \
--query primaryKey -o tsv
5. Share with Thunders:
Relay namespace:
.servicebus.windows.netHybrid connection name:
Send key name:
sendSend key secret: (the send key from step 4)
Thunders will configure these in their platform to connect to your relay.
Environment Variables Reference
Required Variables
Variable | Description | Example |
| Azure Relay namespace |
|
| Hybrid connection name |
|
| SAS key with listen permission |
|
Optional Variables
Variable | Default | Description |
|
| Maximum concurrent browser sessions |
|
| Session timeout in milliseconds (default: 30 minutes) |
| External URL of this instance (used in health responses) |
Ports
Port | Service | Description |
| Nginx (exposed) | Main entry point, health checks, WebSocket proxy |
| Browserless (internal) | Browser engine (proxied through Nginx) |
| Health check (internal) | Health score calculation service |
| Relay bridge (internal) | Azure Relay bridge health endpoint |
Only port 3000 needs to be exposed.
Deployment Options
Option 1: Azure Container Apps (Recommended)
Azure Container Apps provides a managed container hosting platform with built-in scaling, health probes, and HTTPS.
Quick Deploy with Azure CLI
# Login to Azure
az login
# Set variables
RESOURCE_GROUP="rg-thunder-browserless"
LOCATION="westeurope"
ENV_NAME="env-thunder-browserless"
APP_NAME="thunder-browserless"
# Create resource group
az group create
--name $RESOURCE_GROUP
--location $LOCATION
# Create Container Apps environment
az containerapp env create \
--name $ENV_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION
# Deploy the container app
az containerapp create \
--name $APP_NAME \
--resource-group $RESOURCE_GROUP \
--environment $ENV_NAME \
--image <THUNDER_ACR_SERVER>/thunder-browserless:latest \
--registry-server <THUNDER_ACR_SERVER> \
--registry-username <THUNDER_ACR_USERNAME> \
--registry-password <THUNDER_ACR_PASSWORD> \
--target-port 3000 \
--ingress internal \
--cpu 4.0 \
--memory 8.0Gi \
--min-replicas 1 \
--max-replicas 1 \
--env-vars \
CONCURRENT=15 \
TIMEOUT=1800000 \
RELAY_NAMESPACE=<YOUR_RELAY_NAMESPACE> \
RELAY_HYCO=<YOUR_RELAY_HYCO> \
RELAY_LISTEN_KEY=secretref:relay-key \
--secrets \
relay-key=<YOUR_RELAY_LISTEN_KEY>
Deploy with ARM Template
Use the provided ARM template for a more configurable deployment:
az deployment group create \
--resource-group $RESOURCE_GROUP \
--template-file customer-browserless-template.json \
--parameters \
containerAppName=$APP_NAME \
containerAppEnvironmentId=$(az containerapp env show -g $RESOURCE_GROUP -n $ENV_NAME --query id -o tsv) \
containerImage=<THUNDER_ACR_SERVER>/thunder-browserless:latest \
acrServer=<THUNDER_ACR_SERVER> \
acrUsername=<THUNDER_ACR_USERNAME> \
acrPassword=<THUNDER_ACR_PASSWORD> \
relayNamespace=<YOUR_RELAY_NAMESPACE> \
relayHycoName=<YOUR_RELAY_HYCO> \
relayListenKey=<YOUR_RELAY_LISTEN_KEY>
Option 2: Azure Container Instances (ACI)
Azure Container Instances provides serverless container hosting. Simpler but with fewer features than Container Apps.
# Create resource group
az group create --name rg-thunder-browserless --location westeurope
# Deploy container az container create \
--resource-group rg-thunder-browserless \
--name thunder-browserless \
--image <THUNDER_ACR_SERVER>/thunder-browserless:latest \
--registry-login-server <THUNDER_ACR_SERVER> \
--registry-username <THUNDER_ACR_USERNAME> \
--registry-password <THUNDER_ACR_PASSWORD> \
--cpu 4 \
--memory 8 \
--ports 3000 \
--ip-address Private \
--environment-variables \
CONCURRENT=15 \
TIMEOUT=1800000 \
RELAY_NAMESPACE=<YOUR_RELAY_NAMESPACE> \
RELAY_HYCO=<YOUR_RELAY_HYCO> \
RELAY_LISTEN_KEY=<YOUR_RELAY_LISTEN_KEY> \
--restart-policy Always
# Check status az container show \
--resource-group rg-thunder-browserless \
--name thunder-browserless \
--query "{status:instanceView.state, ip:ipAddress.ip}" \
-o table
Option 3: Azure Kubernetes Service (AKS)
For organizations already running AKS clusters.
Create Kubernetes Secret
# Create namespace
kubectl create namespace thunder
# Create image pull secret
kubectl create secret docker-registry thunder-acr \
--namespace thunder \
--docker-server=<THUNDER_ACR_SERVER> \
--docker-username=<THUNDER_ACR_USERNAME> \
--docker-password=<THUNDER_ACR_PASSWORD>
# Create secrets for environment variables
kubectl create secret generic thunder-browserless-secrets \
--namespace thunder \
--from-literal=RELAY_LISTEN_KEY=<YOUR_RELAY_LISTEN_KEY>
Kubernetes Deployment Manifest
Save the following as thunder-browserless.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: thunder-browserless
namespace: thunder
spec: replicas: 1
selector:
matchLabels:
app: thunder-browserless
template:
metadata:
labels:
app: thunder-browserless
spec:
imagePullSecrets:
- name: thunder-acr
containers:
- name: thunder-browserless
image: <THUNDER_ACR_SERVER>/thunder-browserless:latest
ports:
- containerPort: 3000
env:
- name: CONCURRENT
value: "15"
- name: TIMEOUT
value: "1800000"
- name: RELAY_NAMESPACE
value: "<YOUR_RELAY_NAMESPACE>"
- name: RELAY_HYCO
value: "<YOUR_RELAY_HYCO>"
- name: RELAY_LISTEN_KEY
valueFrom:
secretKeyRef:
name: thunder-browserless-secrets
key: RELAY_LISTEN_KEY
resources:
requests:
cpu: "2"
memory: "4Gi"
limits:
cpu: "4"
memory: "8Gi"
startupProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 30
livenessProbe:
httpGet:
path: /health
port: 3000
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: thunder-browserless
namespace: thunder
spec:
type: ClusterIP
selector:
app: thunder-browserless
ports:
- port: 3000
targetPort: 3000
protocol: TCP
Apply:
kubectl apply -f thunder-browserless.yaml
Option 4: Docker (VM or On-Premises)
For running directly on a VM or on-premises server.
Pull and Run
# Login to Thunders's ACR
docker login <THUNDER_ACR_SERVER> \
-u <THUNDER_ACR_USERNAME> \
-p <THUNDER_ACR_PASSWORD>
# Pull the image
docker pull <THUNDER_ACR_SERVER>/thunder-browserless:latest
# Run the container
docker run -d \
--name thunder-browserless \
--restart unless-stopped \
-p 3000:3000 \
-e CONCURRENT=15 \
-e TIMEOUT=1800000 \
-e RELAY_NAMESPACE=<YOUR_RELAY_NAMESPACE> \
-e RELAY_HYCO=<YOUR_RELAY_HYCO> \
-e RELAY_LISTEN_KEY=<YOUR_RELAY_LISTEN_KEY> \
--shm-size=2g \
<THUNDER_ACR_SERVER>/thunder-browserless:latest
Important: The --shm-size=2g flag is required. Chromium uses /dev/shm for shared memory, and the default 64MB is insufficient for browser automation.
Docker Compose
Save as docker-compose.yml:
services:
thunder-browserless:
image: <THUNDER_ACR_SERVER>/thunder-browserless:latest
container_name: thunder-browserless
restart: unless-stopped
ports:
- "3000:3000"
environment:
- CONCURRENT=${CONCURRENT:-15}
- TIMEOUT=${TIMEOUT:-1800000}
- RELAY_NAMESPACE=${RELAY_NAMESPACE}
- RELAY_HYCO=${RELAY_HYCO}
- RELAY_LISTEN_KEY=${RELAY_LISTEN_KEY}
shm_size: 2g
deploy:
resources:
limits:
cpus: "4.0"
memory: 8G
reservations:
cpus: "2.0"
memory: 4G
Create a .env file alongside docker-compose.yml:
RELAY_NAMESPACE=your-relay.servicebus.windows.net
RELAY_HYCO=your-hybrid-connection
RELAY_LISTEN_KEY=your-listen-key
CONCURRENT=15
TIMEOUT=1800000
Run:
docker compose up -d
Health Checks and Monitoring
Health Endpoint
The container exposes health checks at:
GET /health- Returns health status with scoreGET /healthz- Alias for/health
Healthy response (HTTP 200):
{
"origin": "http://localhost:3000",
"healthy": true,
"score": 85,
"error": null,
"pressure": {
"cpu": 15,
"memory": 45,
"running": 2,
"maxConcurrent": 15,
"queued": 0,
"isAvailable": true
},
"scores": {
"cpu": 84,
"memory": 44,
"concurrent": 87
},
"timestamp": "2026-01-15T10:30:00.000Z"
}Unhealthy response (HTTP 503):
Returned when CPU exceeds 95%, memory exceeds 80%, or concurrent sessions are at maximum.
Monitoring Recommendations
Poll
/healthevery 15-30 secondsAlert when
healthyisfalsefor more than 2 consecutive checksMonitor
pressure.runningvspressure.maxConcurrentfor capacity planningTrack
scores.cpuandscores.memorytrends for scaling decisions
Viewing Logs
Azure Container Apps:
az containerapp logs show -g <resource-group> -n <app-name> --follow
Azure Container Instances:
az container logs --resource-group <resource-group> --name <container-name> --follow
Docker:
docker logs -f thunder-browserless
Kubernetes:
kubectl logs -f deployment/thunder-browserless -n thunder
Troubleshooting
Container fails to start
Check logs for missing environment variables:
Required environment variable RELAY_NAMESPACE is not set
Ensure all required environment variables are set.
Verify ACR credentials:
echo '<password>' | docker login <THUNDER_ACR_SERVER> -u <username> --password-stdin
docker pull <THUNDER_ACR_SERVER>/thunder-browserless:latest
Relay bridge not connecting
Verify outbound connectivity on port 443 to
*.servicebus.windows.netCheck relay bridge logs for connection errors
Verify
RELAY_NAMESPACE,RELAY_HYCO, andRELAY_LISTEN_KEYare correctThe relay bridge auto-reconnects every 5 seconds on disconnect
Health check returns 503
CPU threshold exceeded: Reduce
CONCURRENTor allocate more CPUMemory threshold exceeded: Allocate more memory or reduce
CONCURRENTAll sessions busy: Wait for sessions to complete or increase
CONCURRENT
Browser sessions timing out
Increase
TIMEOUT(default: 1800000ms = 30 minutes)Check network latency between your infrastructure and Azure Relay
Shared memory errors
If running with Docker, ensure --shm-size=2g is set. Chromium requires more than the default 64MB of shared memory.
Resource Sizing Guide
Concurrent Sessions | CPU | Memory | Shared Memory |
5 | 2 cores | 4 GB | 1 GB |
15 (default) | 4 cores | 8 GB | 2 GB |
30 | 8 cores | 16 GB | 4 GB |
60 | 16 cores | 32 GB | 8 GB |
These are recommended minimums. Actual requirements depend on the complexity of the web pages being tested.
Network Requirements
Direction | Protocol | Port | Destination | Purpose |
Outbound | HTTPS/WSS | 443 |
| Azure Relay connection |
Outbound | HTTPS | 443 | Target web applications | Browser automation |
Inbound (optional) | HTTP | 3000 | Local network | Health checks (not required for relay) |
No inbound internet access is required for the relay connection to work.
For technical assistance or questions about architecture, please reach out to our support team at [email protected].

