Resource library

QA How-To

How to Run Selenium Grid on AWS ECS (2026)

Learn how to run Selenium Grid on AWS ECS with EC2 capacity, Cloud Map discovery, secure networking, health checks, autoscaling, and runnable tests in CI.

22 min read | 2,251 words

TL;DR

To run Selenium Grid on AWS ECS, place a Hub service and a Chrome-node service on an ECS EC2 cluster, connect them through Cloud Map private DNS, and give each browser container 2 GiB of shared memory. Expose only the Hub WebDriver port to trusted runners, then scale the node service independently.

Key Takeaways

  • Use ECS on EC2 when Chrome nodes need a 2 GiB shared-memory allocation.
  • Run the Selenium Hub and browser nodes as separate ECS services so each tier scales independently.
  • Use AWS Cloud Map to give nodes a stable private DNS name for the Hub.
  • Open Grid event-bus ports only between ECS tasks, and restrict port 4444 to trusted test runners.
  • Pin full Selenium Docker image tags instead of using latest.
  • Scale node desired count from real session demand, not only host CPU.
  • Verify every layer separately: DNS, Grid readiness, node registration, and a real WebDriver session.

To run Selenium Grid on AWS ECS, deploy the Hub and browser nodes as separate ECS services on EC2-backed capacity. Let nodes find the Hub through AWS Cloud Map, reserve enough shared memory for Chrome, and allow Grid traffic only through narrowly scoped security-group rules.

This tutorial builds that layout with AWS CLI 2.32.x, Docker Selenium 4.46.0-20260707, and Selenium Java 4.35.0. You will finish with a remote Chrome test, observable Grid health, and a node service that can scale without replacing the Hub.

The architecture assumes a VPC and two private subnets already exist. It deliberately uses ECS on EC2 instead of Fargate because the ECS sharedMemorySize container setting is not supported for Fargate tasks, while Chrome is substantially more reliable with a real /dev/shm allocation.

What You Will Build

  • One ECS cluster backed by ECS-optimized EC2 instances.
  • A single-replica Selenium Hub service listening on ports 4442, 4443, and 4444.
  • A scalable Chrome node service that registers through hub.grid.local.
  • CloudWatch logs and container health checks for both services.
  • A Maven test that creates a remote WebDriver session and proves the Grid works.

The Hub owns session routing and the Grid UI. Each Chrome task contributes one concurrent browser slot, which makes capacity and failure isolation easy to reason about. If Docker and Grid are new to you, read Docker for Selenium Grid and Docker basics for testers before provisioning AWS resources.

Prerequisites

Use these tested versions or newer compatible patch versions:

Component Version used Check
AWS CLI v2 2.32.x aws --version
Docker Selenium Hub and Node 4.46.0-20260707 immutable image tag
Selenium Java 4.35.0 Maven dependency
Apache Maven 3.9.x mvn -version
Java Temurin 21 LTS java -version
ECS-optimized AMI current Amazon Linux 2023 recommended image SSM public parameter

Configure AWS credentials with permission to manage ECS, EC2, IAM, Cloud Map, Auto Scaling, and CloudWatch Logs. You also need a VPC, two private subnet IDs, and a route from those subnets to pull public images through a NAT gateway. Docker Hub rate limits can affect repeated deployments, so production teams should mirror pinned Selenium images into Amazon ECR.

Set reusable shell variables. Replace every placeholder before continuing:

export AWS_REGION=ap-south-1
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export VPC_ID=vpc-0123456789abcdef0
export PRIVATE_SUBNET_1=subnet-0123456789abcdef0
export PRIVATE_SUBNET_2=subnet-0fedcba9876543210
export ECS_CLUSTER=selenium-grid
export GRID_NAMESPACE=grid.local
export SELENIUM_TAG=4.46.0-20260707

Verify the identity and VPC before creating anything:

aws sts get-caller-identity
aws ec2 describe-vpcs --vpc-ids "$VPC_ID" --region "$AWS_REGION"

Both commands must return JSON without an authorization or not-found error.

Step 1: Create the ECS Cluster and EC2 Capacity

Create the cluster, an instance role, and an instance profile. ECS container instances use the AWS-managed policy to register with the cluster and publish their status.

aws ecs create-cluster --cluster-name "$ECS_CLUSTER" --region "$AWS_REGION"

cat > /tmp/ecs-trust.json <<'JSON'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "ec2.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }]
}
JSON

aws iam create-role \
  --role-name SeleniumGridEcsInstanceRole \
  --assume-role-policy-document file:///tmp/ecs-trust.json
aws iam attach-role-policy \
  --role-name SeleniumGridEcsInstanceRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
aws iam create-instance-profile --instance-profile-name SeleniumGridEcsInstanceProfile
aws iam add-role-to-instance-profile \
  --instance-profile-name SeleniumGridEcsInstanceProfile \
  --role-name SeleniumGridEcsInstanceRole

Create a security group for the instances. No inbound host port is required because awsvpc tasks receive their own elastic network interfaces.

export INSTANCE_SG=$(aws ec2 create-security-group \
  --group-name selenium-grid-ecs-instances \
  --description "ECS hosts for Selenium Grid" \
  --vpc-id "$VPC_ID" --region "$AWS_REGION" \
  --query GroupId --output text)

export ECS_AMI=$(aws ssm get-parameter \
  --name /aws/service/ecs/optimized-ami/amazon-linux-2023/recommended/image_id \
  --region "$AWS_REGION" --query 'Parameter.Value' --output text)

Create a launch template and Auto Scaling group. The example uses m6i.large, which has enough memory for one 2 GiB Chrome task plus the Hub and ECS overhead. Increase instance size or count before increasing browser density.

cat > /tmp/launch-template.json <<JSON
{
  "ImageId": "$ECS_AMI",
  "InstanceType": "m6i.large",
  "IamInstanceProfile": {"Name": "SeleniumGridEcsInstanceProfile"},
  "SecurityGroupIds": ["$INSTANCE_SG"],
  "UserData": "$(printf '#!/bin/bash\necho ECS_CLUSTER=%s >> /etc/ecs/ecs.config\n' "$ECS_CLUSTER" | base64 | tr -d '\n')",
  "MetadataOptions": {"HttpTokens": "required", "HttpEndpoint": "enabled"}
}
JSON

export LT_ID=$(aws ec2 create-launch-template \
  --launch-template-name selenium-grid-ecs \
  --launch-template-data file:///tmp/launch-template.json \
  --region "$AWS_REGION" --query 'LaunchTemplate.LaunchTemplateId' --output text)

aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name selenium-grid-ecs \
  --launch-template "LaunchTemplateId=$LT_ID,Version=\$Latest" \
  --min-size 1 --max-size 4 --desired-capacity 2 \
  --vpc-zone-identifier "$PRIVATE_SUBNET_1,$PRIVATE_SUBNET_2" \
  --region "$AWS_REGION"

Verify that instances register. IAM profile propagation and boot can take several minutes:

aws ecs list-container-instances \
  --cluster "$ECS_CLUSTER" --region "$AWS_REGION" \
  --query 'containerInstanceArns'

Continue only when the array contains at least one ARN.

Step 2: Create Private Discovery and Task Networking

Cloud Map gives the Hub a stable name even when ECS replaces its task and private IP. Create a private DNS namespace and a dedicated task security group.

export NS_OPERATION=$(aws servicediscovery create-private-dns-namespace \
  --name "$GRID_NAMESPACE" --vpc "$VPC_ID" \
  --region "$AWS_REGION" --query OperationId --output text)

aws servicediscovery get-operation \
  --operation-id "$NS_OPERATION" --region "$AWS_REGION"

export NAMESPACE_ID=$(aws servicediscovery list-namespaces \
  --region "$AWS_REGION" \
  --query "Namespaces[?Name=='$GRID_NAMESPACE'].Id | [0]" --output text)

export HUB_DISCOVERY_ARN=$(aws servicediscovery create-service \
  --name hub --namespace-id "$NAMESPACE_ID" \
  --dns-config "NamespaceId=$NAMESPACE_ID,DnsRecords=[{Type=A,TTL=10}],RoutingPolicy=MULTIVALUE" \
  --health-check-custom-config FailureThreshold=1 \
  --region "$AWS_REGION" --query 'Service.Arn' --output text)

export TASK_SG=$(aws ec2 create-security-group \
  --group-name selenium-grid-tasks \
  --description "Selenium Grid task traffic" \
  --vpc-id "$VPC_ID" --region "$AWS_REGION" \
  --query GroupId --output text)

Allow Grid traffic from the same task security group. Ports 4442 and 4443 carry event-bus publish and subscribe traffic; 4444 serves WebDriver and the UI.

aws ec2 authorize-security-group-ingress \
  --group-id "$TASK_SG" --protocol tcp --port 4442 \
  --source-group "$TASK_SG" --region "$AWS_REGION"
aws ec2 authorize-security-group-ingress \
  --group-id "$TASK_SG" --protocol tcp --port 4443 \
  --source-group "$TASK_SG" --region "$AWS_REGION"
aws ec2 authorize-security-group-ingress \
  --group-id "$TASK_SG" --protocol tcp --port 4444 \
  --source-group "$TASK_SG" --region "$AWS_REGION"

For an external CI runner, add port 4444 from that runner's security group through an internal load balancer. Do not open 4444 to 0.0.0.0/0; Grid can create browsers and reach internal sites, so unauthenticated public exposure is dangerous.

Verify namespace and service creation:

aws servicediscovery list-services \
  --region "$AWS_REGION" \
  --query "Services[?Name=='hub'].[Name,Arn]" --output table

The table must show one hub service ARN.

Step 3: Register the Hub Task Definition

Create the ECS task execution role if your account does not already have it. This role lets the ECS agent pull images and send logs, while application permissions would belong in a separate task role.

cat > /tmp/ecs-task-trust.json <<'JSON'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "ecs-tasks.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }]
}
JSON

aws iam create-role \
  --role-name SeleniumGridTaskExecutionRole \
  --assume-role-policy-document file:///tmp/ecs-task-trust.json
aws iam attach-role-policy \
  --role-name SeleniumGridTaskExecutionRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
aws logs create-log-group --log-group-name /ecs/selenium-grid --region "$AWS_REGION"

If create-role or create-log-group reports that the resource exists, inspect and reuse it. Create the Hub definition with named port mappings and a readiness health check:

cat > /tmp/selenium-hub-task.json <<JSON
{
  "family": "selenium-hub",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["EC2"],
  "executionRoleArn": "arn:aws:iam::$AWS_ACCOUNT_ID:role/SeleniumGridTaskExecutionRole",
  "containerDefinitions": [{
    "name": "selenium-hub",
    "image": "selenium/hub:$SELENIUM_TAG",
    "essential": true,
    "cpu": 512,
    "memoryReservation": 768,
    "portMappings": [
      {"name": "event-publish", "containerPort": 4442, "protocol": "tcp"},
      {"name": "event-subscribe", "containerPort": 4443, "protocol": "tcp"},
      {"name": "webdriver", "containerPort": 4444, "protocol": "tcp"}
    ],
    "environment": [
      {"name": "SE_SESSION_REQUEST_TIMEOUT", "value": "300"},
      {"name": "SE_SESSION_RETRY_INTERVAL", "value": "5"}
    ],
    "healthCheck": {
      "command": ["CMD-SHELL", "curl -fsS http://localhost:4444/status | grep -q '\"ready\": true' || exit 1"],
      "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 30
    },
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "/ecs/selenium-grid",
        "awslogs-region": "$AWS_REGION",
        "awslogs-stream-prefix": "hub"
      }
    }
  }]
}
JSON

aws ecs register-task-definition \
  --cli-input-json file:///tmp/selenium-hub-task.json \
  --region "$AWS_REGION"

Verify the active revision and image:

aws ecs describe-task-definition --task-definition selenium-hub \
  --region "$AWS_REGION" \
  --query 'taskDefinition.{revision:revision,image:containerDefinitions[0].image}'

The image must end in 4.46.0-20260707; a floating latest tag makes rollbacks and incident analysis ambiguous.

Step 4: Run Selenium Grid on AWS ECS as a Hub Service

Create the Hub service with one desired task and register it in Cloud Map. Because Selenium Grid has one central session map in this layout, keep the Hub at one replica. High availability requires a distributed Grid architecture and externalized components, not merely setting desired count to two.

aws ecs create-service \
  --cluster "$ECS_CLUSTER" \
  --service-name selenium-hub \
  --task-definition selenium-hub \
  --desired-count 1 \
  --launch-type EC2 \
  --network-configuration "awsvpcConfiguration={subnets=[$PRIVATE_SUBNET_1,$PRIVATE_SUBNET_2],securityGroups=[$TASK_SG],assignPublicIp=DISABLED}" \
  --service-registries "registryArn=$HUB_DISCOVERY_ARN" \
  --health-check-grace-period-seconds 60 \
  --region "$AWS_REGION"

aws ecs wait services-stable \
  --cluster "$ECS_CLUSTER" --services selenium-hub \
  --region "$AWS_REGION"

Inspect task and service events if the waiter exits unsuccessfully:

aws ecs describe-services \
  --cluster "$ECS_CLUSTER" --services selenium-hub \
  --region "$AWS_REGION" \
  --query 'services[0].{running:runningCount,desired:desiredCount,events:events[0:5].[createdAt,message]}'

Verification succeeds when running and desired both equal 1 and recent events contain no placement or image-pull failure. To test DNS and /status privately, use an EC2 or CI runner in the VPC and run curl -fsS http://hub.grid.local:4444/status. The returned JSON must contain "ready": true.

Step 5: Register the Chrome Node Task

The node must resolve the Hub name and connect to all three Grid ports. Set one session per task to prevent two Chrome processes from competing for the same reserved memory. sharedMemorySize is expressed in MiB.

cat > /tmp/selenium-chrome-task.json <<JSON
{
  "family": "selenium-node-chrome",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["EC2"],
  "executionRoleArn": "arn:aws:iam::$AWS_ACCOUNT_ID:role/SeleniumGridTaskExecutionRole",
  "containerDefinitions": [{
    "name": "chrome",
    "image": "selenium/node-chrome:$SELENIUM_TAG",
    "essential": true,
    "cpu": 1024,
    "memoryReservation": 2048,
    "linuxParameters": {"sharedMemorySize": 2048},
    "environment": [
      {"name": "SE_EVENT_BUS_HOST", "value": "hub.grid.local"},
      {"name": "SE_EVENT_BUS_PUBLISH_PORT", "value": "4442"},
      {"name": "SE_EVENT_BUS_SUBSCRIBE_PORT", "value": "4443"},
      {"name": "SE_NODE_MAX_SESSIONS", "value": "1"},
      {"name": "SE_NODE_OVERRIDE_MAX_SESSIONS", "value": "true"},
      {"name": "SE_NODE_SESSION_TIMEOUT", "value": "300"}
    ],
    "healthCheck": {
      "command": ["CMD-SHELL", "curl -fsS http://localhost:5555/status >/dev/null || exit 1"],
      "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 45
    },
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "/ecs/selenium-grid",
        "awslogs-region": "$AWS_REGION",
        "awslogs-stream-prefix": "chrome"
      }
    }
  }]
}
JSON

aws ecs register-task-definition \
  --cli-input-json file:///tmp/selenium-chrome-task.json \
  --region "$AWS_REGION"

Verify that ECS accepts the EC2-only shared-memory setting:

aws ecs describe-task-definition --task-definition selenium-node-chrome \
  --region "$AWS_REGION" \
  --query 'taskDefinition.containerDefinitions[0].{image:image,shm:linuxParameters.sharedMemorySize,maxSessions:environment[?name==`SE_NODE_MAX_SESSIONS`].value|[0]}'

Expected values are the pinned node image, 2048, and 1. A tiny /dev/shm often surfaces as Chrome crashes, disconnected renderers, or sessions that fail only under load.

Step 6: Start and Verify the Browser Nodes

Create two node tasks. ECS can place them across available instances, and each task receives its own private address. Nodes do not need a Cloud Map record because they initiate registration with the Hub.

aws ecs create-service \
  --cluster "$ECS_CLUSTER" \
  --service-name selenium-chrome \
  --task-definition selenium-node-chrome \
  --desired-count 2 \
  --launch-type EC2 \
  --network-configuration "awsvpcConfiguration={subnets=[$PRIVATE_SUBNET_1,$PRIVATE_SUBNET_2],securityGroups=[$TASK_SG],assignPublicIp=DISABLED}" \
  --placement-strategy type=spread,field=attribute:ecs.availability-zone \
  --region "$AWS_REGION"

aws ecs wait services-stable \
  --cluster "$ECS_CLUSTER" --services selenium-chrome \
  --region "$AWS_REGION"

Check service counts and then query the Grid status from inside the VPC:

aws ecs describe-services \
  --cluster "$ECS_CLUSTER" --services selenium-chrome \
  --region "$AWS_REGION" \
  --query 'services[0].{running:runningCount,pending:pendingCount,desired:desiredCount}'

curl -fsS http://hub.grid.local:4444/status | jq '{ready: .value.ready, nodes: (.value.nodes | length), slots: ([.value.nodes[].slots[]] | length)}'

Expect running: 2, nodes: 2, and slots: 2. If ECS is healthy but Grid shows zero nodes, inspect Chrome logs with aws logs tail /ecs/selenium-grid --log-stream-name-prefix chrome --since 10m. DNS failure points to VPC DNS or Cloud Map; connection refusal points to security groups, Hub readiness, or the wrong event-bus port. For deeper queue diagnostics, use the Selenium Grid session queue monitoring tutorial.

Step 7: Run a Real Remote WebDriver Test

Create a small Maven project on a runner that can resolve hub.grid.local. This can be an ECS task, CodeBuild project attached to the VPC, self-hosted CI runner, or bastion used only for verification.

<!-- pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>grid-smoke</artifactId>
  <version>1.0.0</version>
  <properties>
    <maven.compiler.release>21</maven.compiler.release>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-java</artifactId>
      <version>4.35.0</version>
    </dependency>
  </dependencies>
  <build><plugins><plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>3.5.1</version>
    <configuration><mainClass>GridSmoke</mainClass></configuration>
  </plugin></plugins></build>
</project>

Add a complete test program. The Hub URL can be overridden for an internal load balancer without changing the code.

// src/main/java/GridSmoke.java
import java.net.URI;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

public class GridSmoke {
  public static void main(String[] args) throws Exception {
    String gridUrl = System.getenv().getOrDefault(
        "SELENIUM_GRID_URL", "http://hub.grid.local:4444");
    ChromeOptions options = new ChromeOptions();
    options.addArguments("--headless=new", "--window-size=1440,900");

    WebDriver driver = new RemoteWebDriver(URI.create(gridUrl).toURL(), options);
    try {
      driver.get("https://www.selenium.dev/selenium/web/web-form.html");
      driver.findElement(By.name("my-text")).sendKeys("ECS Grid works");
      driver.findElement(By.cssSelector("button")).click();
      new WebDriverWait(driver, Duration.ofSeconds(10)).until(
          ExpectedConditions.textToBe(By.id("message"), "Received!"));
      System.out.println("PASS session=" + ((RemoteWebDriver) driver).getSessionId());
    } finally {
      driver.quit();
    }
  }
}

Run and verify it:

mvn -q compile exec:java

The command must print PASS session= followed by a session identifier and exit with code zero. A successful /status call alone is insufficient because it does not prove that Chrome launches, navigation works, or session cleanup succeeds. For a larger framework, follow build a Selenium Java framework from scratch and add parallel execution to a framework.

Step 8: Scale After You Run Selenium Grid on AWS ECS

Scale browser capacity by updating the node service, not the Hub. Confirm that EC2 capacity can satisfy each task's 1024 CPU units, 2 GiB reserved memory, ENI requirement, and 2 GiB shared-memory allocation.

aws ecs update-service \
  --cluster "$ECS_CLUSTER" --service selenium-chrome \
  --desired-count 4 --region "$AWS_REGION"

aws ecs describe-services \
  --cluster "$ECS_CLUSTER" --services selenium-chrome \
  --region "$AWS_REGION" \
  --query 'services[0].{running:runningCount,pending:pendingCount,desired:desiredCount}'

Wait until running equals desired, then verify four nodes through /status. If tasks stay pending, read the newest ECS service event before adding instances. It will distinguish insufficient CPU, memory, ENIs, or matching container instances.

Use CloudWatch alarms for ECS running task count, service deployment failures, and EC2 capacity. Add a scheduled synthetic WebDriver session because container health checks cannot detect a broken browser binary or external test-site path. Selenium exposes Grid status and GraphQL endpoints on port 4444; collect node count, slot state, session duration, and queue depth from a trusted monitoring task.

Autoscaling on average ECS CPU alone can react too late: queued requests sit on the Hub while idle node tasks show little CPU. A better control loop publishes queue length or available-slot count as a custom CloudWatch metric, then uses target tracking or step scaling with conservative cooldowns. Keep enough warm nodes for normal traffic because a new EC2 instance plus image pull is slower than a test timeout. For CI design patterns, see parallel test sharding in CI and Jenkins pipeline for Selenium.

Operationally, pin image digests after qualification, mirror them into ECR, enable ECS managed tags, and roll out a new task revision to a small nonproduction Grid first. Store videos or downloads outside the ephemeral node filesystem if they must survive task replacement. Terminate every WebDriver session in finally; abandoned sessions occupy slots until SE_NODE_SESSION_TIMEOUT expires.

Troubleshooting

Problem: Chrome tasks remain in PENDING -> Read the latest ECS service event. Increase the Auto Scaling group or instance size when CPU or memory is insufficient. If the message mentions ENIs, choose supported instance capacity, spread tasks differently, or enable awsvpcTrunking after reviewing its prerequisites.

Problem: Nodes log UnknownHostException for hub.grid.local -> Confirm the Cloud Map namespace belongs to the same VPC, VPC attributes enableDnsSupport and enableDnsHostnames are enabled, and the Hub task is registered. Query the A record from a host inside the VPC; a private namespace does not resolve over the public internet.

Problem: Nodes resolve the Hub but cannot register -> Check inbound rules from the task security group to itself on TCP 4442 and 4443. Confirm SE_EVENT_BUS_HOST, publish port, and subscribe port exactly match the Hub. Then compare Hub and node image tags so Grid components run the same release.

Problem: Tests receive SessionNotCreatedException although Grid is ready -> Inspect .value.nodes[].slots in /status. Ready means the Router responds; it does not guarantee a free matching slot. Add Chrome tasks, reduce test parallelism, or fix requested capabilities that do not match registered stereotypes.

Problem: Chrome crashes with DevToolsActivePort or renderer errors -> Verify the task definition reports sharedMemorySize: 2048 and runs on EC2. Do not hide capacity problems with --disable-dev-shm-usage as the first fix. Check task memory, EC2 memory pressure, and Chrome logs before changing flags.

Problem: A deployment keeps replacing healthy-looking tasks -> Inspect container health status and run the exact health-check command in the image. Increase startPeriod for slow pulls or cold starts, confirm curl exists in the pinned image, and read stopped-task reason plus container exitCode rather than relying only on service events.

Best Practices

  • Treat port 4444 as privileged infrastructure. Keep it private, restrict inbound sources, and add authenticated access at a proxy if humans need the UI.
  • Use separate task families and ECS services for the Hub and every browser type. Chrome and Firefox then scale and upgrade independently.
  • Set explicit CPU, memory, shared memory, maximum sessions, and timeouts. Defaults conceal capacity assumptions.
  • Pin the dated Selenium tag across all Grid components. Test a new revision before production rollout.
  • Keep one browser session per node task unless load testing proves a higher density is stable on your chosen instance.
  • Capture Grid, ECS, EC2, and test-runner telemetry with the same run identifier. This turns a generic timeout into a traceable session lifecycle.
  • Drain test traffic before reducing desired count. ECS can stop a task that still owns a browser unless your deployment and test scheduler coordinate.

Interview Questions and Answers

The structured interview section below covers architecture, networking, shared memory, scaling, security, and failure diagnosis. Practice explaining why each AWS component exists instead of only reciting the CLI sequence. For broader preparation, review CI/CD troubleshooting interview questions for QA.

Control Cost Before It Controls You

A Selenium Grid on ECS bills for every second a node task runs, so an idle grid left up overnight is pure waste. Three habits keep the bill honest. First, scale node services to zero when no suite is running and let the queue scale them back up, rather than parking a fixed fleet of browser tasks. Second, cap the per-suite task count with an explicit desired-count and a service quota so a runaway matrix cannot spawn hundreds of Fargate tasks and a surprise invoice. Third, tag every grid task with the pipeline and branch that launched it, then alert on any task older than your longest expected suite, because a leaked node from a cancelled build is the most common source of ECS cost creep. Measure cost per thousand tests, not per hour, so the number tracks value delivered.

Where To Go Next

You now have the minimum production-shaped topology to run Selenium Grid on AWS ECS: one discoverable Hub, independently scalable Chrome nodes, pinned images, private networking, logs, and a real session check. Add an internal load balancer when runners live in other VPC-connected networks, then automate these resources with CloudFormation, CDK, or Terraform.

Next, compare this deployment with running tests on a Selenium Grid in Kubernetes. Improve delivery with GitLab CI for test automation, or upload evidence from this project to your QAJobFit dashboard. You can also rehearse architecture questions in QA practice.

Before increasing concurrency, measure session creation latency, queue time, browser memory, and test duration at one, two, and four nodes. Those observations give you defensible scaling thresholds and expose application bottlenecks that adding browsers cannot solve.

Interview Questions and Answers

How would you design Selenium Grid on Amazon ECS?

I would run the Hub and each browser type as separate ECS services on private subnets. Cloud Map gives the Hub stable discovery, and security groups permit event-bus traffic only between tasks. Browser services scale independently, while the Hub remains a single replica unless I deliberately implement Selenium's distributed architecture.

Why choose ECS on EC2 instead of Fargate for browser nodes?

Chrome benefits from a sizable `/dev/shm`, and ECS exposes that through `linuxParameters.sharedMemorySize` on EC2 tasks. Fargate does not support that task-definition parameter. EC2 also gives more control over browser density and underlying capacity, at the cost of managing instances.

What do Selenium Grid ports 4442, 4443, and 4444 do?

Ports 4442 and 4443 are the event-bus publish and subscribe endpoints used by Grid components. Port 4444 serves WebDriver commands, Grid status, GraphQL, and the UI. I permit event-bus ports only within the task security group and expose 4444 only to trusted runners or an internal proxy.

How would you prove an ECS-hosted Grid is healthy?

I check ECS desired versus running counts, container health, and stopped-task reasons first. Then I query `/status` for readiness, registered nodes, and available slots. Finally I create a real RemoteWebDriver session, navigate, assert page state, and quit, because infrastructure health alone does not prove the browser path works.

How do you scale Selenium Grid nodes safely?

I scale browser service desired count from queue or free-slot demand and ensure the EC2 capacity provider can add hosts. I keep one session per task initially, account for CPU, memory, shared memory, and ENIs, and maintain warm capacity. Scale-in must drain or coordinate with the test scheduler so active sessions are not terminated.

A Chrome ECS service is running, but no node appears in Grid. What do you inspect?

I read the node logs for DNS and connection errors, resolve the Cloud Map Hub name from the task network, and test ports 4442 and 4443. I verify the security-group source rule, event-bus environment variables, Hub readiness, and matching Selenium image versions. This separates ECS health from Grid registration health.

Why is adding a second Hub replica not automatic high availability?

A conventional Hub owns session routing and in-memory Grid state. Two independent Hub tasks behind a load balancer do not automatically share a consistent session map, so later commands may reach the wrong instance. True high availability needs an intentionally distributed Grid design and stateful components configured for that topology.

Frequently Asked Questions

Can Selenium Grid run on AWS ECS Fargate?

The Hub can run on Fargate, but the standard ECS `sharedMemorySize` setting required for a larger Chrome `/dev/shm` is not supported on Fargate. This tutorial uses ECS on EC2 so browser nodes receive a controlled 2 GiB shared-memory allocation.

Why use AWS Cloud Map for Selenium Grid on ECS?

An ECS task's private IP changes when the task is replaced. Cloud Map maintains a stable private DNS name for the Hub, so node task definitions do not embed an address that will become stale.

Which Selenium Grid ports must be open on ECS?

Nodes need TCP 4442 and 4443 to reach the Hub event bus. Test runners need TCP 4444 for WebDriver and the Grid status endpoint. Restrict all three ports to known security groups or trusted network ranges.

How many Chrome sessions should one ECS task run?

Start with one session per Chrome task. It gives predictable CPU and memory isolation, makes failed tasks affect one test, and lets desired task count represent browser capacity directly. Raise density only after load tests demonstrate stable resource headroom.

How do I expose Selenium Grid to a CI runner?

Place the runner in the same VPC or connect its network through supported private routing, then use an internal load balancer on Hub port 4444. Allow inbound traffic from the runner security group and avoid exposing the Grid directly to the public internet.

How should I autoscale Selenium nodes in ECS?

Publish Grid queue length or available slots as a custom CloudWatch metric and scale the Chrome service from that demand signal. Coordinate it with EC2 capacity-provider scaling and keep warm capacity because instance launch and image pull time can exceed a test's patience.

Why pin a dated Docker Selenium image tag?

A dated full tag identifies the exact Grid and browser image used by a run. Floating tags can change between deployments, weakening reproducibility and making a rollback uncertain.

Related Guides