Q34 of 37 · Selenium

How would you set up Selenium Grid 4 in Docker for CI usage?

SeleniumSeniorseleniumgriddockercisenior

Short answer

Short answer: Docker Compose with a hub container and chrome/firefox node containers, scaled with `--scale chrome=N`. Tests connect via `RemoteWebDriver` to `http://localhost:4444`. For CI, start before tests, tear down after, and pin image versions to match your test code's Selenium version.

Detail

The minimal viable docker-compose.yml:

version: '3.9'
services:
  selenium-hub:
    image: selenium/hub:4.20
    ports:
      - '4442:4442'
      - '4443:4443'
      - '4444:4444'

  chrome:
    image: selenium/node-chrome:4.20
    shm_size: '2g'
    depends_on: [selenium-hub]
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - SE_NODE_MAX_SESSIONS=3

  firefox:
    image: selenium/node-firefox:4.20
    shm_size: '2g'
    depends_on: [selenium-hub]
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - SE_NODE_MAX_SESSIONS=2

Critical details that bite you in CI:

  1. shm_size: 2g — without this, Chrome crashes on heavy pages. The default Docker shm is 64MB; far too small.

  2. Pin versionsselenium/hub:4.20 not :latest. Match your client library's Selenium version (selenium-java:4.20) to the Grid version. Mismatches cause obscure session-creation errors.

  3. Test code points at the hub:

WebDriver driver = new RemoteWebDriver(
    new URL("http://localhost:4444"), new ChromeOptions()
);
  1. File uploads: ((RemoteWebDriver) driver).setFileDetector(new LocalFileDetector()) — without it, Selenium tries to read the file from the node's filesystem, not yours.

  2. Scaling:

docker compose up -d --scale chrome=4 --scale firefox=2

Each replica gets SE_NODE_MAX_SESSIONS slots, so 4 chrome × 3 sessions = 12 concurrent Chromes.

CI integration (GitHub Actions example):

- name: Start Grid
  run: docker compose up -d
- name: Wait for hub
  run: |
    for i in {1..30}; do
      curl -sf http://localhost:4444/status && break
      sleep 1
    done
- name: Tests
  run: mvn test -DGRID_URL=http://localhost:4444
- name: Tear down
  if: always()
  run: docker compose down

Alternatives I'd consider before standing this up:

  • Sauce Labs / BrowserStack / LambdaTest — managed Grid. If your CI bill is small, the build-vs-buy maths often favours buy.
  • Selenium-on-K8s (KEDA + Grid) — for very large suites with elastic demand.

For most teams, the Docker Compose setup above runs forever — it's a 50-line yaml that handles 90% of needs.

// EXAMPLE

docker-compose.yml

version: '3.9'
services:
  selenium-hub:
    image: selenium/hub:4.20
    ports: ['4444:4444', '4442:4442', '4443:4443']
    environment:
      - SE_SESSION_REQUEST_TIMEOUT=300

  chrome:
    image: selenium/node-chrome:4.20
    shm_size: '2g'
    depends_on: [selenium-hub]
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - SE_NODE_MAX_SESSIONS=3

// WHAT INTERVIEWERS LOOK FOR

shm_size, version pinning, RemoteWebDriver pointing at localhost:4444, LocalFileDetector for uploads, and an honest acknowledgement that managed services may be cheaper than self-hosting.

// COMMON PITFALL

Forgetting shm_size and chasing 'Chrome crashed' errors that look like a Selenium bug. Or running latest images and getting protocol mismatches with your test code's Selenium version.