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.
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.
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
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.
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?
The host reaches a published port; the browser is inside the Compose network and reaches the AUT by service DNS.
What should you wait for before running the test: container running, Grid ready, or browser page ready?
All are separate states. First Grid readiness, then session creation, then the AUT-specific page condition.
Why capture returned capabilities if the image tag already says Chrome?
Capabilities provide the actual negotiated session browser/version/platform and are better evidence for the specific run.
A navigation to 127.0.0.1:8000 fails in the browser
container. Should you add a 30-second wait?
No. That is a routing/addressing problem, not synchronization.
Why avoid docker system prune -a in cleanup?
It is not scoped to this lab and can destroy unrelated Docker state.
Official references and current-version notes
- SeleniumHQ/docker-selenium — official images, Compose, Dynamic Grid, troubleshooting
- Docker Selenium 4.47.0-20260808 release
- Official Selenium Grid Helm chart
- Selenium Grid documentation
- Grid CLI/configuration options
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.