Docker and Cypress — Reproducible Test Environments with Official Images

Cypress tests depend on system-level libraries (libnss, libatk, libgtk), specific browser versions, and a correctly configured display server. Installing these dependencies on every developer’s machine and every CI runner is tedious and error-prone. Docker solves this: the official Cypress Docker images include everything pre-installed — browsers, system libraries, Node.js, and Cypress itself. One docker run command gives you a reproducible test environment that works identically on macOS, Linux, Windows, and every CI platform.

Official Cypress Docker Images

Cypress maintains several Docker image variants, each tailored to a specific use case.

// ── Cypress Docker image variants ──

const DOCKER_IMAGES = [
    {
        image: 'cypress/base',
        contains: 'Node.js + OS dependencies (no browsers)',
        size: '~800 MB',
        use: 'When you install Cypress and browsers yourself',
    },
    {
        image: 'cypress/browsers',
        contains: 'Node.js + OS deps + Chrome + Firefox + Edge',
        size: '~2.5 GB',
        use: 'Most common — run tests on any included browser',
    },
    {
        image: 'cypress/included',
        contains: 'Everything in browsers + Cypress pre-installed',
        size: '~3 GB',
        use: 'Zero-install: just mount your project and run',
    },
];


// ── Using cypress/included — the fastest way to run ──

/*
  # Run tests with zero installation
  docker run -it \
    -v $(pwd):/e2e \
    -w /e2e \
    cypress/included:13.7.0 \
    --browser chrome

  # Explanation:
  #   -v $(pwd):/e2e     → Mount your project into the container
  #   -w /e2e            → Set working directory to your project
  #   cypress/included   → Image with everything pre-installed
  #   --browser chrome   → Run on Chrome
*/


// ── Docker Compose for app + tests ──

const DOCKER_COMPOSE = `
# docker-compose.cypress.yml
version: '3.8'

services:
  # Your application
  app:
    build: .
    ports:
      - '3000:3000'
    environment:
      - NODE_ENV=test
      - DATABASE_URL=postgres://db:5432/testdb

  # Test database
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: testdb
      POSTGRES_PASSWORD: test

  # Cypress test runner
  cypress:
    image: cypress/included:13.7.0
    depends_on:
      - app
    environment:
      - CYPRESS_BASE_URL=http://app:3000
      - CYPRESS_VIDEO=false
    working_dir: /e2e
    volumes:
      - ./:/e2e
      - /e2e/node_modules    # Exclude host node_modules
    command: --browser chrome
`;


// ── Dockerfile for custom Cypress image ──

const CUSTOM_DOCKERFILE = `
# Dockerfile.cypress
FROM cypress/browsers:latest

WORKDIR /app

# Install project dependencies (cached layer)
COPY package.json package-lock.json ./
RUN npm ci

# Copy test files
COPY cypress/ ./cypress/
COPY cypress.config.ts ./

# Default command
CMD ["npx", "cypress", "run", "--browser", "chrome"]
`;


// ── Docker best practices for Cypress ──

const DOCKER_BEST_PRACTICES = [
    'Use cypress/included for zero-install, cypress/browsers for custom setups',
    'Pin image versions (cypress/included:13.7.0, not :latest) for reproducibility',
    'Exclude host node_modules with a volume mount to avoid platform conflicts',
    'Set CYPRESS_VIDEO=false in containers to save disk space and upload time',
    'Use depends_on with health checks to ensure the app is ready before tests run',
    'Mount only necessary files — exclude .git, build artifacts, IDE configs',
    'Set shm_size: 2g if Chrome crashes with out-of-memory errors in Docker',
];

console.log('Cypress Docker Images:');
DOCKER_IMAGES.forEach(img => {
  console.log(`\n  ${img.image}`);
  console.log(`    Contains: ${img.contains}`);
  console.log(`    Size: ${img.size}`);
  console.log(`    Use: ${img.use}`);
});
Note: The cypress/included image contains Cypress itself pre-installed as a global npm package. You do not need to run npm install or npx cypress install — the container is ready to execute tests immediately. Just mount your project directory and run. This eliminates the single slowest step in most CI pipelines: installing Cypress (which downloads a ~300MB binary). For teams that customise their Cypress installation (plugins, preprocessors), cypress/browsers is more appropriate because it lets you run npm ci with your own package.json.
Tip: Use Docker Compose to run your app, database, and Cypress tests together in a single docker compose up command. The depends_on directive ensures the app starts before Cypress, and CYPRESS_BASE_URL=http://app:3000 uses Docker’s internal networking so Cypress reaches the app without exposing ports to the host. This setup produces a fully self-contained test environment that any team member can start with one command.
Warning: Always pin Docker image versions: cypress/included:13.7.0, not cypress/included:latest. The :latest tag changes when new versions are released, potentially introducing breaking changes in your CI pipeline without any code change on your side. Pinned versions ensure your test environment is identical today, next week, and next month. Update the version deliberately in a PR where you can verify compatibility.

Common Mistakes

Mistake 1 — Using :latest tag for Cypress Docker images

❌ Wrong: cypress/included:latest — CI pipeline breaks unexpectedly when Cypress releases a new version.

✅ Correct: cypress/included:13.7.0 — pinned version, updated deliberately via a PR with test verification.

Mistake 2 — Mounting host node_modules into the container

❌ Wrong: Host node_modules (compiled for macOS) are mounted into a Linux container — native modules crash with incompatible binary errors.

✅ Correct: Add - /e2e/node_modules as an anonymous volume to exclude host node_modules, and run npm ci inside the container.

🧠 Test Yourself

What is the key advantage of cypress/included over cypress/browsers?