Docker for Testing

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

VMContainer
Full guest OS + hypervisorProcess group sharing host kernel
GB size, minutes to startMB size, seconds to start
Strong isolationProcess-level isolation (cgroups, namespaces)
Heavy for transient test envsIdeal for transient test envs

27.2 Core vocabulary

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

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.