Chapter 20Lesson 02~240 minutes

Deploying Grid with Containers, Kubernetes, and Cloud Infrastructure: Guided Hands-On Workflow

This workflow turns the mental model into a disposable local lab. The Grid is a pinned official Selenium standalone Chrome container; the AUT is a second service on the same Compose network; the Python test runner stays on the host. That topology makes the browser-to-AUT network boundary visible and reproducible.

Docker ComposeRemoteWebDriverNetwork DNSEvidenceCleanup

Learning objectives

  • Create a two-service Docker Compose lab with a pinned Selenium browser image.
  • Prove Grid readiness before starting a test.
  • Run RemoteWebDriver from the host while the browser reaches the AUT by Compose service DNS.
  • Capture session/capability/screenshot evidence without enabling broad video/log collection.
  • Tear down containers, network, and optional volumes deterministically.

1. Disposable lab topology

The test runner connects to 127.0.0.1:4444 because Docker publishes the Grid endpoint to the host loopback address. The browser does not navigate to host loopback. It navigates to http://aut:8000/, where aut is Docker Compose service DNS on the private lab network.

Different callers use different network names

The following diagram visualizes the relationships described in Disposable lab topology. Read the nodes in sequence and use the arrows to connect the conceptual state changes to the explanation around the diagram.

flowchart LR
 H[Host Python test] -->|127.0.0.1:4444| S[selenium service]
 S -->|browser navigates to aut:8000| A[aut service]
 S --- N[Compose network: lab]
 A --- N

2. Create the fixture and Compose definition

Create an empty directory, then add fixture/index.html and compose.yaml. The AUT contains no accounts, credentials, uploads, or production data.

<!doctype html>
<html lang="en"><head><meta charset="utf-8"><title>Chapter 20 AUT</title></head>
<body><main><h1>Disposable container Grid fixture</h1><p data-testid="status">container-grid-ok</p></main></body></html>

The following example makes the Create the fixture and Compose definition behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

services:
  selenium:
    image: selenium/standalone-chrome:4.47.0-20260808
    shm_size: 2gb
    ports:
      - "127.0.0.1:4444:4444"
    depends_on:
      - aut
    networks: [lab]
  aut:
    image: python:3.13-alpine
    working_dir: /site
    command: ["python", "-m", "http.server", "8000", "--bind", "0.0.0.0"]
    volumes:
      - ./fixture:/site:ro
    networks: [lab]
networks:
  lab:
    name: selenium-ch20-lab
Why only port 4444 is published

The AUT does not need a host port. Keeping it internal to the Compose network demonstrates that the browser container can reach a service by service DNS without exposing the fixture to the LAN.

3. Start Grid and verify readiness

The following example makes the Start Grid and verify readiness behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

docker compose pull
docker compose up -d
docker compose ps
curl -fsS http://127.0.0.1:4444/status

Do not infer readiness from “container is running.” The official images include health behavior, and Grid exposes /status. Your test should start only after the status payload reports ready. In CI, use a bounded poll with a deadline rather than a giant fixed sleep.

import json, time, urllib.request

deadline = time.monotonic() + 30
last = None
while time.monotonic() < deadline:
    try:
        with urllib.request.urlopen("http://127.0.0.1:4444/status", timeout=2) as r:
            last = json.load(r)
        if last.get("value", {}).get("ready") is True:
            print("Grid ready")
            break
    except OSError:
        pass
    time.sleep(0.25)
else:
    raise RuntimeError(f"Grid not ready before deadline: {last!r}")

4. Run a RemoteWebDriver session and prove causality

The following example makes the Run a RemoteWebDriver session and prove causality behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

from pathlib import Path
import json
from selenium import webdriver
from selenium.webdriver.common.by import By

GRID = "http://127.0.0.1:4444"
AUT = "http://aut:8000/"
out = Path("evidence")
out.mkdir(exist_ok=True)

options = webdriver.ChromeOptions()
driver = webdriver.Remote(command_executor=GRID, options=options)
try:
    driver.get(AUT)
    heading = driver.find_element(By.CSS_SELECTOR, "[data-testid='status']")
    assert heading.text == "container-grid-ok"
    evidence = {
        "session_id": driver.session_id,
        "browserName": driver.capabilities.get("browserName"),
        "browserVersion": driver.capabilities.get("browserVersion"),
        "platformName": driver.capabilities.get("platformName"),
        "url": driver.current_url,
        "title": driver.title,
        "status": heading.text,
    }
    (out / "session.json").write_text(json.dumps(evidence, indent=2), encoding="utf-8")
    driver.save_screenshot(str(out / "viewport.png"))
finally:
    driver.quit()

The host changes Grid session state by creating a RemoteWebDriver session. The browser container changes AUT observation state by loading http://aut:8000/. The test writes only two local evidence artifacts: a small JSON file and one viewport screenshot. It does not persist the browser profile or entire container filesystem.

5. Inspect returned state and container logs

The following example makes the Inspect returned state and container logs behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

cat evidence/session.json
docker compose ps
docker compose logs --no-color selenium | tail -n 80

Correlate the Python session_id with Grid logs when available. Record browser name/version/platform from returned capabilities—not from the image name alone. Image metadata says what was deployed; returned capabilities say what the created session actually negotiated.

6. Optional video/log capture without turning it into a default

Official Docker Selenium supports VNC/noVNC and video patterns, but video is not free: recording consumes CPU and storage, and screenshots/video may contain secrets or PII. Keep the mandatory lab screenshot-only. Enable video only for a bounded diagnostic need, store it in a per-session path, and apply retention. Selenium 4.47 added per-session Dynamic Grid video subfolders via SE_VIDEO_SESSION_SUBFOLDER.

7. Challenge: choose the correct boundary

You change the AUT URL in test_grid.py from http://aut:8000/ to http://127.0.0.1:8000/. The test runner still reaches Grid, but navigation fails. Before changing Selenium waits or locators, predict which layer is wrong and which evidence you would inspect.

Expected reasoning

The failure is browser-container networking. Confirm Grid session creation succeeded, inspect current URL/navigation error and container DNS/network membership, then restore the service DNS name. A locator retry cannot repair routing.

8. Deterministic cleanup

The following example makes the Deterministic cleanup behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

docker compose down --remove-orphans
rm -rf evidence
# Optional if you created named/anonymous lab volumes:
docker volume ls --filter label=com.docker.compose.project
# Do not prune unrelated Docker resources.

Use project-scoped teardown. Avoid docker system prune -a as a course cleanup step because it can remove unrelated developer images, caches, and stopped containers.

Knowledge check

Why does the host test use 127.0.0.1:4444 while the browser uses aut:8000?

What should you wait for before running the test: container running, Grid ready, or browser page ready?

Why capture returned capabilities if the image tag already says Chrome?

A navigation to 127.0.0.1:8000 fails in the browser container. Should you add a 30-second wait?

Why avoid docker system prune -a in cleanup?

Next lesson

Deploying Grid with Containers, Kubernetes, and Cloud Infrastructure: Configuration, Design Patterns, and Trade-Offs

Continue with Deploying Grid with Containers, Kubernetes, and Cloud Infrastructure: Configuration, Design Patterns, and Trade-Offs. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

Official references and current-version notes

Version baseline — August 2026

These lessons pin Selenium Python and Grid concepts to 4.47.0, Docker Selenium image tag 4.47.0-20260808, and Helm chart 0.58.0. The nightly images track Selenium 4.48.0-SNAPSHOT and are intentionally excluded from the mandatory path.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.