or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

container-management.mddocker-compose.mdindex.mdnetworking.mdutilities.mdwait-strategies.md
tile.json

docker-compose.mddocs/

Docker Compose

Manage multi-container environments using Docker Compose files with programmatic configuration and per-service wait strategies.

Capabilities

DockerComposeEnvironment Class

Manages Docker Compose environments with configuration for services, builds, and wait strategies.

/**
 * Creates a Docker Compose environment from compose files
 * @param composeFilePath - Directory containing compose files
 * @param composeFiles - Single file name or array of compose file names
 */
class DockerComposeEnvironment {
  constructor(composeFilePath: string, composeFiles: string | string[]);

  /**
   * Build images before starting services
   */
  withBuild(): this;

  /**
   * Set environment variables for all services
   * @param environment - Key-value pairs of environment variables
   */
  withEnvironment(environment: Environment): this;

  /**
   * Load environment variables from file
   * @param environmentFile - Path to environment file
   */
  withEnvironmentFile(environmentFile: string): this;

  /**
   * Set compose profiles to activate
   * @param profiles - Profile names to enable
   */
  withProfiles(...profiles: string[]): this;

  /**
   * Disable recreation of existing containers
   */
  withNoRecreate(): this;

  /**
   * Set image pull policy for services
   * @param pullPolicy - Pull policy configuration
   */
  withPullPolicy(pullPolicy: ImagePullPolicy): this;

  /**
   * Set default wait strategy for all services
   * @param waitStrategy - Wait strategy to use
   */
  withDefaultWaitStrategy(waitStrategy: WaitStrategy): this;

  /**
   * Set wait strategy for specific service
   * @param containerName - Service name from compose file
   * @param waitStrategy - Wait strategy to use for this service
   */
  withWaitStrategy(containerName: string, waitStrategy: WaitStrategy): this;

  /**
   * Set startup timeout for all services
   * @param startupTimeoutMs - Timeout in milliseconds
   */
  withStartupTimeout(startupTimeoutMs: number): this;

  /**
   * Set custom project name
   * @param projectName - Project name for compose environment
   */
  withProjectName(projectName: string): this;

  /**
   * Set additional docker-compose client options
   * @param options - Partial compose client options
   */
  withClientOptions(options: Partial<ComposeOptions>): this;

  /**
   * Start the compose environment
   * @param services - Optional array of specific services to start
   * @returns Promise resolving to started environment
   */
  up(services?: Array<string>): Promise<StartedDockerComposeEnvironment>;
}

Usage Examples:

import { DockerComposeEnvironment, Wait } from 'testcontainers';

// Basic compose environment
const environment = await new DockerComposeEnvironment(
  './docker',
  'docker-compose.yml'
).up();

// Get individual containers
const web = environment.getContainer('web');
const db = environment.getContainer('postgres');

// Cleanup
await environment.down();

// Advanced configuration with wait strategies
const env = await new DockerComposeEnvironment(
  './infrastructure',
  ['docker-compose.yml', 'docker-compose.test.yml']
)
  .withBuild()
  .withEnvironment({
    NODE_ENV: 'test',
    LOG_LEVEL: 'debug'
  })
  .withProfiles('test', 'monitoring')
  .withWaitStrategy('api', Wait.forLogMessage('Server started on port'))
  .withWaitStrategy('postgres', Wait.forLogMessage('database system is ready'))
  .withWaitStrategy('redis', Wait.forLogMessage('Ready to accept connections'))
  .withStartupTimeout(120000)
  .up();

// Start only specific services
const partialEnv = await new DockerComposeEnvironment(
  './docker',
  'docker-compose.yml'
)
  .withEnvironmentFile('.env.test')
  .up(['api', 'database']);

StartedDockerComposeEnvironment Class

Represents a running Docker Compose environment with access to individual containers.

/**
 * Running Docker Compose environment
 */
class StartedDockerComposeEnvironment {
  /**
   * Get container by service name
   * @param containerName - Service name from compose file
   * @returns Started container instance
   */
  getContainer(containerName: string): StartedTestContainer;

  /**
   * Stop all containers in the environment
   * @returns Promise resolving to stopped environment
   */
  stop(): Promise<StoppedDockerComposeEnvironment>;

  /**
   * Stop and remove all containers, networks, and volumes
   * @param options - Down options including timeout and volume removal
   * @returns Promise resolving to downed environment
   */
  down(options?: Partial<ComposeDownOptions>): Promise<DownedDockerComposeEnvironment>;

  /**
   * Async disposal support for automatic cleanup
   */
  [Symbol.asyncDispose](): Promise<void>;
}

Usage Examples:

import { DockerComposeEnvironment, Wait } from 'testcontainers';

const environment = await new DockerComposeEnvironment(
  './docker',
  'docker-compose.yml'
)
  .withWaitStrategy('api', Wait.forHttp('/health', 3000))
  .withWaitStrategy('db', Wait.forHealthCheck())
  .up();

// Access individual containers
const api = environment.getContainer('api');
const apiHost = api.getHost();
const apiPort = api.getMappedPort(3000);
console.log(`API available at http://${apiHost}:${apiPort}`);

const db = environment.getContainer('db');
const dbPort = db.getMappedPort(5432);

// Execute commands on specific services
const result = await db.exec(['psql', '-U', 'postgres', '-c', 'SELECT version()']);
console.log(result.output);

// Get logs from specific service
const logs = await api.logs({ tail: 50 });
logs.on('data', (line) => console.log('API:', line));

// Cleanup - stop but keep containers
await environment.stop();

// Or cleanup - remove everything
await environment.down({ removeVolumes: true });

// Using async disposal (automatic cleanup)
{
  await using environment = await new DockerComposeEnvironment(
    './docker',
    'docker-compose.yml'
  ).up();

  // Use environment
  const api = environment.getContainer('api');
  // ... tests ...

  // Automatically cleaned up when scope exits
}

StoppedDockerComposeEnvironment Class

Represents a stopped Docker Compose environment that can be fully removed.

/**
 * Stopped Docker Compose environment
 */
class StoppedDockerComposeEnvironment {
  /**
   * Remove all containers, networks, and volumes
   * @param options - Down options including timeout and volume removal
   * @returns Promise resolving to downed environment
   */
  down(options?: Partial<ComposeDownOptions>): Promise<DownedDockerComposeEnvironment>;
}

DownedDockerComposeEnvironment Class

Terminal state representing a fully removed Docker Compose environment.

/**
 * Fully removed Docker Compose environment (terminal state)
 */
class DownedDockerComposeEnvironment {
  // No operations available - environment is fully cleaned up
}

Complex Example

Example showing a full integration test setup with multiple services:

import { DockerComposeEnvironment, Wait } from 'testcontainers';
import { describe, it, before, after } from 'node:test';
import assert from 'node:assert';

describe('E2E Integration Tests', () => {
  let environment: StartedDockerComposeEnvironment;
  let apiUrl: string;

  before(async () => {
    // Start full environment with custom configuration
    environment = await new DockerComposeEnvironment(
      './infrastructure',
      ['docker-compose.yml', 'docker-compose.integration.yml']
    )
      .withBuild()
      .withEnvironment({
        NODE_ENV: 'test',
        DATABASE_URL: 'postgresql://test:test@postgres:5432/testdb',
        REDIS_URL: 'redis://redis:6379'
      })
      .withProfiles('integration')
      // Wait strategies for each service
      .withWaitStrategy('postgres', Wait.forLogMessage(/database system is ready/))
      .withWaitStrategy('redis', Wait.forLogMessage('Ready to accept connections'))
      .withWaitStrategy('rabbitmq', Wait.forLogMessage('Server startup complete'))
      .withWaitStrategy('api', Wait.forHttp('/health', 3000).forStatusCode(200))
      .withWaitStrategy('worker', Wait.forLogMessage('Worker started'))
      .withStartupTimeout(180000) // 3 minutes for all services
      .up();

    // Get API connection details
    const api = environment.getContainer('api');
    apiUrl = `http://${api.getHost()}:${api.getMappedPort(3000)}`;
  });

  after(async () => {
    // Cleanup with volume removal
    await environment.down({ removeVolumes: true, timeout: 30000 });
  });

  it('should process messages through the full pipeline', async () => {
    // Test using the full environment
    const response = await fetch(`${apiUrl}/api/messages`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ message: 'test' })
    });

    assert.strictEqual(response.status, 201);

    // Verify worker processed the message
    const worker = environment.getContainer('worker');
    const logs = await worker.logs({ since: Date.now() - 5000 });
    // Check logs for processing confirmation
  });
});

Types

type Environment = { [key: string]: string };

interface ComposeDownOptions {
  timeout?: number;
  removeVolumes?: boolean;
}

interface ComposeOptions {
  filePath: string;
  files: string | string[];
  projectName: string;
  commandOptions?: string[];
  composeOptions?: string[];
  environment?: NodeJS.ProcessEnv;
  logger?: Logger;
  executable?: ComposeExecutableOptions;
}

type ComposeExecutableOptions =
  | {
      executablePath: string;
      options?: string[] | (string | string[])[];
      standalone?: never;
    }
  | {
      executablePath?: string;
      options?: never;
      standalone: true;
    };

interface ImagePullPolicy {
  shouldPull(): boolean;
}

interface WaitStrategy {
  withStartupTimeout(startupTimeoutMs: number): WaitStrategy;
}