|

Test Environment Management: Docker + Testcontainers for Stable CI Pipelines

Your tests pass locally but fail in CI. The root cause is almost never the test — it is the environment. Docker and Testcontainers solve this by giving every test run an identical, disposable environment.

Why Environment Instability Kills CI

  • Different database versions between local and CI
  • Shared test data that tests modify in unpredictable order
  • Third-party services that are unavailable or rate-limited in CI
  • OS-level differences (macOS local vs Linux CI runner)

Testcontainers: Disposable Infrastructure in Code

import { PostgreSqlContainer } from '@testcontainers/postgresql';
import { test, expect } from '@playwright/test';

let container;
let dbUrl;

test.beforeAll(async () => {
  container = await new PostgreSqlContainer()
    .withDatabase('testdb')
    .withUsername('test')
    .withPassword('test')
    .start();
  
  dbUrl = container.getConnectionUri();
  // Seed test data
  await seedDatabase(dbUrl);
});

test.afterAll(async () => {
  await container.stop();
});

test('user creation persists to database', async ({ request }) => {
  const response = await request.post('/api/users', {
    data: { name: 'Test', email: 'test@test.com' }
  });
  expect(response.status()).toBe(201);
  
  // Verify directly in the disposable database
  const users = await queryDb(dbUrl, 'SELECT * FROM users');
  expect(users).toHaveLength(1);
});

Docker Compose for Full Stack Testing

version: '3.8'
services:
  app:
    build: .
    environment:
      DATABASE_URL: postgres://test:test@db:5432/testdb
      REDIS_URL: redis://cache:6379
    depends_on:
      db: { condition: service_healthy }
      cache: { condition: service_started }

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U test"]
      interval: 5s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine

  playwright:
    image: mcr.microsoft.com/playwright:v1.59.0
    command: npx playwright test
    depends_on:
      app: { condition: service_started }
    volumes:
      - ./test-results:/app/test-results

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.