27Docker for Testing
What you will master here
- Containers vs VMs — the real difference
- Dockerfile, image, container, registry
- Running Playwright in the official Docker image
- docker-compose for app + DB + test runner
- Testcontainers — spinning real services from tests
- Multi-stage builds, caching layers
- Common test-infra pitfalls
27.1 Container vs VM
| VM | Container |
|---|---|
| Full guest OS + hypervisor | Process group sharing host kernel |
| GB size, minutes to start | MB size, seconds to start |
| Strong isolation | Process-level isolation (cgroups, namespaces) |
| Heavy for transient test envs | Ideal for transient test envs |
27.2 Core vocabulary
- Image — read-only template (filesystem + metadata). Built from a Dockerfile.
- Container — running instance of an image.
- Dockerfile — declarative recipe to build an image.
- Registry — where images are stored/shared (Docker Hub, GHCR, ECR).
- Layer — each Dockerfile step creates a cached layer. Order matters for cache efficiency.
27.3 Basic commands
docker pull mcr.microsoft.com/playwright:v1.50.0-jammy docker images # list local docker ps # running containers docker ps -a # all containers docker run -it --rm ubuntu bash # ephemeral interactive docker run -d --name mydb -p 5432:5432 -e POSTGRES_PASSWORD=secret postgres docker logs mydb docker exec -it mydb psql -U postgres docker stop mydb && docker rm mydb docker build -t my-tests . docker push myregistry/my-tests:1.0
27.4 Dockerfile for a Playwright project
FROM mcr.microsoft.com/playwright:v1.50.0-jammy WORKDIR /app # Copy lockfile first → layer cache reuses when only code changes COPY package*.json ./ RUN npm ci # Copy source COPY . . # Default command CMD ["npx", "playwright", "test"]
27.5 docker-compose — app + DB + tests
version: '3.8'
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: app_test
ports: ["5432:5432"]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 2s
app:
build: ./app
depends_on:
db: { condition: service_healthy }
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/app_test
ports: ["3000:3000"]
tests:
image: mcr.microsoft.com/playwright:v1.50.0-jammy
depends_on: [app]
environment:
BASE_URL: http://app:3000
volumes:
- .:/work
working_dir: /work
command: npx playwright test
docker compose up --abort-on-container-exit --exit-code-from tests
27.6 Testcontainers (Java + others)
Library that spins up real services (Postgres, Kafka, Selenium Grid) from inside a JUnit test. Each test gets a fresh DB; no shared state.
@Testcontainers
class UserRepoTest {
@Container static PostgreSQLContainer<?> pg = new PostgreSQLContainer<>("postgres:16");
@Test
void createsUser() throws Exception {
DataSource ds = createDataSource(pg.getJdbcUrl(), pg.getUsername(), pg.getPassword());
/* run migrations, exercise repo, assert */
}
}
Available containers: Postgres, MySQL, Mongo, Redis, RabbitMQ, Kafka, Localstack (AWS), Selenium Standalone, Playwright (via GenericContainer).
27.7 Multi-stage builds
FROM node:20 AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM mcr.microsoft.com/playwright:v1.50.0-jammy WORKDIR /app COPY --from=build /app/dist ./dist COPY --from=build /app/node_modules ./node_modules COPY tests ./tests COPY playwright.config.ts ./ CMD ["npx", "playwright", "test"]
Final image is smaller — no build tools, source maps, dev deps.
27.8 Common pitfalls
- Wrong base image — using
node:20-alpinefor Playwright → no Chrome dependencies, install fails. Use the official Playwright image. - Layer cache busting — copying everything before
npm cimeans every code change invalidates the install layer. Copy lockfile first. - Network between containers — inside docker-compose, use service names (
db,app) notlocalhost. - Permission issues — official Playwright image runs as
pwuser; volume mounts may fail on chown. RunUSER rootor adjust ownership. - Shared memory — Chrome needs more than the default 64 MB /dev/shm in containers. Set
--shm-size=1gbor use--ipc=host. - Disk bloat — old layers accumulate.
docker system prune -aweekly.
27.9 docker for CI
Most CI runners (GitHub Actions, GitLab CI, Jenkins) support running steps inside a container. Pin the version of the Playwright image to match local — keeps trace/screenshot rendering identical and removes "passes locally fails CI" caused by image drift.
Module 50 — Docker Q&A
Container vs VM — one-line difference?
VM virtualises hardware (guest OS + hypervisor). Container virtualises the OS (shared kernel, isolated process tree). Containers start in seconds, weigh MBs; VMs minutes and GBs.
Why use a Docker image for Playwright tests?
Deterministic environment — same Chromium build, fonts, OS libraries everywhere. Eliminates "works on my Mac" failures. CI and dev share the image; visual baselines stay stable.
What's the difference between an image and a container?
Image is a read-only template (filesystem + metadata). Container is a running (or stopped) instance of an image with its own writable layer. You can spawn many containers from one image.
Why does layer order in a Dockerfile matter?
Docker caches each layer. If a layer's inputs are unchanged, it's reused. Common pattern: copy lockfile, install deps, THEN copy source. Code changes don't bust the deps layer; rebuild is seconds not minutes.
How do containers talk to each other in docker-compose?
Compose creates a default network; services are reachable by service name (
http://app:3000 from the tests service). Don't use localhost — that's the container itself.What is Testcontainers and when do you use it?
A library that spins up real services as Docker containers from your test code. Each test gets a fresh isolated DB/Kafka/Redis. Replaces flaky shared test environments with per-test environments.
Why might Chrome crash inside a container?
Default /dev/shm is too small (64 MB). Chrome needs more. Fix:
docker run --shm-size=1gb or --ipc=host to share the host's shared memory.What's a multi-stage build?
A Dockerfile with multiple FROM statements. Build artefacts in an early stage; copy only what you need into a slim final image. Strips out build tools, source code, dev deps.
How do you clean up disk space from Docker?
docker system prune -a removes stopped containers, unused images, networks, build cache. Run weekly on CI runners or you'll fill the disk.Should test data live inside a container or in a volume?
Ephemeral data (per-test seeded) — inside the container; throw away on teardown. Long-lived data (image baselines, reports) — mounted volume so it survives container restarts.