or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-testcontainers--elasticsearch

Elasticsearch module for Testcontainers that simplifies creation and management of Elasticsearch containers for testing

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@testcontainers/elasticsearch@11.10.x

To install, run

npx @tessl/cli install tessl/npm-testcontainers--elasticsearch@11.10.0

index.mddocs/

Elasticsearch Testcontainer

The Elasticsearch Testcontainer module provides a specialized container class for creating and managing Elasticsearch instances in Docker containers for testing purposes. It extends the Testcontainers library with pre-configured Elasticsearch-specific settings including single-node discovery, disabled SSL, configurable authentication, and automatic health checks.

Key Information for Agents

Required Dependencies:

  • @testcontainers/elasticsearch (this package)
  • testcontainers core package is required
  • @elastic/elasticsearch client library (for interacting with Elasticsearch)
  • Docker daemon must be running and accessible
  • Docker image must be pullable (network access required)

Default Behaviors:

  • Default password: 'changeme' (configurable via withPassword())
  • Default username: 'elastic' (fixed, not configurable)
  • Default HTTP port: 9200 (exposed and mapped to random host port)
  • Default discovery type: single-node (Elasticsearch runs in single-node mode)
  • Default SSL: Disabled (xpack.security.http.ssl.enabled=false)
  • Default JVM memory: 2GB (set via -Xmx2G)
  • Default startup timeout: 120 seconds
  • Default wait strategy: HTTP endpoint check with basic auth
  • Container automatically stops when disposed (using await using syntax)
  • Data is ephemeral (lost when container stops)

Threading Model:

  • ElasticsearchContainer instances are not thread-safe (create per test)
  • StartedElasticsearchContainer instances are thread-safe for read operations (getPort, getHttpUrl, etc.)
  • Container lifecycle methods (start(), stop(), restart()) are not thread-safe
  • Multiple containers can run concurrently (each gets unique ports)
  • Elasticsearch client operations are independent of container thread safety

Lifecycle:

  • Container must be started before use (await container.start())
  • Container automatically waits for Elasticsearch to be ready (HTTP health check)
  • Container stops automatically with await using syntax (TypeScript 5.2+)
  • Manual cleanup: await container.stop() when done
  • Container restart: await container.restart() (data will be lost)
  • Container disposal: Automatically cleaned up when using await using

Exceptions:

  • ContainerStartupException - Container failed to start within timeout
  • DockerException - Docker daemon not available or image pull failed
  • ConnectionException - Unable to connect to started container
  • TimeoutException - Startup timeout exceeded (120 seconds default)
  • ElasticsearchException - Elasticsearch-specific errors (from client library)

Edge Cases:

  • Port conflicts: Container automatically maps to random available port (use getPort() to get actual port)
  • Image pull failures: Requires network access and valid image name
  • Startup timeout: May need to increase timeout for slower systems (withStartupTimeout())
  • Multiple versions: Can run multiple Elasticsearch versions simultaneously (different containers)
  • Container restart: Data is lost on restart (ephemeral storage)
  • Memory constraints: 2GB default may be insufficient for large datasets (can be overridden via environment variables)
  • Authentication: Password must be set before start() is called
  • Network isolation: Containers can be isolated using withNetworkMode()
  • File system: Container file system is ephemeral (use withCopyContentToContainer() for persistent setup)
  • Health check: Wait strategy checks HTTP endpoint with basic auth (may fail if Elasticsearch version incompatible)

Package Information

  • Package Name: @testcontainers/elasticsearch
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install @testcontainers/elasticsearch --save-dev

Core Imports

import { ElasticsearchContainer, StartedElasticsearchContainer } from '@testcontainers/elasticsearch';

For CommonJS:

const { ElasticsearchContainer, StartedElasticsearchContainer } = require('@testcontainers/elasticsearch');

Architecture

The module provides two main classes:

  • ElasticsearchContainer: Configuration and startup class that extends GenericContainer from testcontainers. Provides Elasticsearch-specific defaults and configuration methods.
  • StartedElasticsearchContainer: Represents a running container with convenience methods for retrieving connection details and interacting with the container.

The container is pre-configured with:

  • Port 9200 exposed for HTTP connections
  • Single-node discovery mode (discovery.type=single-node)
  • SSL disabled (xpack.security.http.ssl.enabled=false)
  • JVM memory limit set to 2GB
  • Basic authentication enabled with username elastic
  • Wait strategy that checks the HTTP endpoint before considering container ready
  • 120 second startup timeout

API Reference

ElasticsearchContainer

/**
 * Creates a new Elasticsearch container configuration
 * @param image - Docker image name (e.g., 'elasticsearch:8.18.1', 'elasticsearch:7.17.28')
 * @throws {DockerException} - If Docker daemon is not available
 */
class ElasticsearchContainer extends GenericContainer {
  constructor(image: string);

  /**
   * Configure custom password for Elasticsearch authentication
   * @param password - Custom password for the 'elastic' user (default: 'changeme')
   * @returns This container instance for method chaining
   * @throws {IllegalArgumentException} - If password is empty or invalid
   */
  withPassword(password: string): this;

  /**
   * Start the Elasticsearch container
   * @returns Promise that resolves to a running container instance
   * @throws {ContainerStartupException} - If container fails to start
   * @throws {TimeoutException} - If container doesn't become ready within timeout
   * @throws {DockerException} - If Docker operations fail
   */
  start(): Promise<StartedElasticsearchContainer>;
}

Inherited Methods from GenericContainer:

  • withExposedPorts(...ports: number[]) - Expose additional ports
  • withEnvironment(key: string, value: string) - Set environment variables
  • withEnvironment(env: Record<string, string>) - Set multiple environment variables
  • withWaitStrategy(strategy: WaitStrategy) - Configure custom wait strategy
  • withStartupTimeout(timeout: Duration) - Set startup timeout (default: 120 seconds)
  • withCopyContentToContainer(content: Transferable) - Copy files/content to container
  • withNetworkMode(mode: string) - Configure network mode (e.g., 'host', 'bridge')
  • withCommand(command: string[]) - Override container command
  • withNetwork(network: Network) - Attach to specific network
  • withReuse(reuse: boolean) - Enable container reuse
  • withPrivilegedMode(privileged: boolean) - Run in privileged mode
  • withExtraHost(host: string, ipAddress: string) - Add extra host entry
  • withLogConsumer(consumer: Consumer<OutputFrame>) - Configure log consumption

StartedElasticsearchContainer

/**
 * Represents a running Elasticsearch container with convenience methods
 */
class StartedElasticsearchContainer extends AbstractStartedContainer {
  /**
   * Get the host port mapped to Elasticsearch's HTTP port (9200)
   * @returns The host port number
   * @throws {Error} - If container is not running
   */
  getPort(): number;

  /**
   * Get the complete HTTP URL for connecting to Elasticsearch
   * @returns URL in format 'http://host:port'
   * @throws {Error} - If container is not running
   */
  getHttpUrl(): string;

  /**
   * Get the username for Elasticsearch authentication
   * @returns Username (always 'elastic')
   */
  getUsername(): string;

  /**
   * Get the password for Elasticsearch authentication
   * @returns Password (default 'changeme' or custom password set via withPassword)
   */
  getPassword(): string;
}

Inherited Methods from AbstractStartedContainer:

  • stop() - Stop the container (returns Promise)
  • restart() - Restart the container (returns Promise, data will be lost)
  • getHost() - Get container host (returns string)
  • getMappedPort(containerPort: number) - Get specific mapped port
  • getName() - Get container name (returns string)
  • getId() - Get container ID (returns string)
  • exec(command: string[]) - Execute command in container (returns Promise<ExecResult>)
  • logs() - Get container logs (returns Stream)
  • copyFilesToContainer(source: string, destination: string) - Copy files to running container
  • getNetworkNames() - Get network names container is attached to
  • getNetworkId(networkName: string) - Get network ID for specific network

Usage Patterns

Basic Container Setup

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Client } from '@elastic/elasticsearch';

// Start an Elasticsearch container with default settings
const container = await new ElasticsearchContainer('elasticsearch:8.18.1').start();

// Create an Elasticsearch client
const client = new Client({
  node: container.getHttpUrl(),
  auth: {
    username: container.getUsername(),
    password: container.getPassword()
  }
});

// Use the client to interact with Elasticsearch
await client.indices.create({ index: 'test-index' });
const exists = await client.indices.exists({ index: 'test-index' });

// Container will automatically stop when disposed
await container.stop();

Automatic Disposal with await using

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Client } from '@elastic/elasticsearch';

// Use with automatic disposal (TypeScript 5.2+)
await using container = await new ElasticsearchContainer('elasticsearch:8.18.1').start();

const client = new Client({
  node: container.getHttpUrl(),
  auth: {
    username: container.getUsername(),
    password: container.getPassword()
  }
});

// Container automatically stops when out of scope
// No need to call container.stop()

Custom Password Configuration

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Client } from '@elastic/elasticsearch';

// Configure container with custom password
const container = await new ElasticsearchContainer('elasticsearch:8.18.1')
  .withPassword('mySecurePassword123')
  .start();

const client = new Client({
  node: container.getHttpUrl(),
  auth: {
    username: container.getUsername(),
    password: container.getPassword() // Returns 'mySecurePassword123'
  }
});

Test Setup with Jest/Vitest

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Client } from '@elastic/elasticsearch';

describe('Elasticsearch Tests', () => {
  let container: StartedElasticsearchContainer;
  let client: Client;

  beforeAll(async () => {
    container = await new ElasticsearchContainer('elasticsearch:8.18.1').start();
    client = new Client({
      node: container.getHttpUrl(),
      auth: {
        username: container.getUsername(),
        password: container.getPassword()
      }
    });
  });

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

  it('should create and query an index', async () => {
    // Create an index
    await client.indices.create({ index: 'people' });

    // Index a document
    await client.index({
      index: 'people',
      id: '1',
      document: { name: 'John Doe', age: 30 }
    });

    // Refresh to make document searchable
    await client.indices.refresh({ index: 'people' });

    // Query the document
    const result = await client.get({ index: 'people', id: '1' });
    expect(result._source).toEqual({ name: 'John Doe', age: 30 });
  });
});

Test Setup with await using

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Client } from '@elastic/elasticsearch';

describe('Elasticsearch Tests', () => {
  it('should create and query an index', async () => {
    await using container = await new ElasticsearchContainer('elasticsearch:8.18.1').start();

    const client = new Client({
      node: container.getHttpUrl(),
      auth: {
        username: container.getUsername(),
        password: container.getPassword()
      }
    });

    // Create an index
    await client.indices.create({ index: 'people' });

    // Index a document
    await client.index({
      index: 'people',
      id: '1',
      document: { name: 'John Doe', age: 30 }
    });

    // Refresh to make document searchable
    await client.indices.refresh({ index: 'people' });

    // Query the document
    const result = await client.get({ index: 'people', id: '1' });
    expect(result._source).toEqual({ name: 'John Doe', age: 30 });
  });
});

Container Restart

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Client } from '@elastic/elasticsearch';

await using container = await new ElasticsearchContainer('elasticsearch:8.18.1').start();

const client = new Client({
  node: container.getHttpUrl(),
  auth: { username: container.getUsername(), password: container.getPassword() }
});

await client.indices.create({ index: 'test' });

// Restart the container (note: data will be lost)
await container.restart();

// Container is ready to use again with the same connection details
// Note: getHttpUrl() and getPort() return the same values after restart
const health = await client.cluster.health();

Testing Multiple Elasticsearch Versions

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Client } from '@elastic/elasticsearch';

const versions = ['elasticsearch:7.17.28', 'elasticsearch:8.18.1'];

for (const version of versions) {
  describe(`Elasticsearch ${version}`, () => {
    it('should work correctly', async () => {
      await using container = await new ElasticsearchContainer(version).start();

      const client = new Client({
        node: container.getHttpUrl(),
        auth: {
          username: container.getUsername(),
          password: container.getPassword()
        }
      });

      await client.indices.create({ index: 'test' });
      expect(await client.indices.exists({ index: 'test' })).toBe(true);
    });
  });
}

Custom Environment Variables

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';

// Override JVM memory settings
const container = await new ElasticsearchContainer('elasticsearch:8.18.1')
  .withEnvironment('ES_JAVA_OPTS', '-Xmx4G -Xms4G')
  .start();

Custom Startup Timeout

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Duration } from 'testcontainers';

// Increase startup timeout for slower systems
const container = await new ElasticsearchContainer('elasticsearch:8.18.1')
  .withStartupTimeout(Duration.ofSeconds(300))
  .start();

Network Configuration

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Network } from 'testcontainers';

// Create isolated network
const network = await Network.newNetwork();

const container = await new ElasticsearchContainer('elasticsearch:8.18.1')
  .withNetwork(network)
  .start();

Copying Files to Container

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { GenericContainer } from 'testcontainers';

// Copy configuration files to container
const container = await new ElasticsearchContainer('elasticsearch:8.18.1')
  .withCopyContentToContainer([
    {
      content: 'cluster.name: my-cluster',
      target: '/usr/share/elasticsearch/config/elasticsearch.yml'
    }
  ])
  .start();

Log Consumption

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { OutputFrame } from 'testcontainers';

const container = await new ElasticsearchContainer('elasticsearch:8.18.1')
  .withLogConsumer((frame: OutputFrame) => {
    console.log(frame.getUtf8String());
  })
  .start();

Supported Elasticsearch Versions

The module supports both major Elasticsearch versions:

  • Elasticsearch 7.x (e.g., elasticsearch:7.17.28)
  • Elasticsearch 8.x (e.g., elasticsearch:8.18.1)

Images can be found in the Elasticsearch Docker Hub registry.

Version Compatibility Notes:

  • Elasticsearch 7.x and 8.x have different default configurations
  • Authentication requirements may vary between versions
  • API compatibility may differ (use appropriate @elastic/elasticsearch client version)
  • Some features may not be available in older versions

Default Configuration

The ElasticsearchContainer applies the following default configuration:

SettingValueDescription
HTTP Port9200Exposed and mapped to random host port
UsernameelasticFixed authentication username
PasswordchangemeDefault password (configurable via withPassword())
Discovery Typesingle-nodeElasticsearch runs in single-node mode
SSLDisabledHTTP SSL is disabled for testing (xpack.security.http.ssl.enabled=false)
JVM Memory2GBMaximum heap size set to -Xmx2G
Startup Timeout120 secondsMaximum time to wait for container to be ready
Wait StrategyHTTP endpoint checkWaits for HTTP endpoint to respond with basic auth
Data PersistenceEphemeralData is lost when container stops

Integration with Elasticsearch Clients

This module is designed to work seamlessly with Elasticsearch client libraries:

Official Elasticsearch JavaScript Client

import { Client } from '@elastic/elasticsearch';
import { ElasticsearchContainer } from '@testcontainers/elasticsearch';

await using container = await new ElasticsearchContainer('elasticsearch:8.18.1').start();

const client = new Client({
  node: container.getHttpUrl(),
  auth: {
    username: container.getUsername(),
    password: container.getPassword()
  }
});

// Use the client for all Elasticsearch operations
await client.ping();
await client.indices.create({ index: 'my-index' });
await client.index({ index: 'my-index', document: { field: 'value' } });
await client.indices.refresh({ index: 'my-index' });
const result = await client.search({ index: 'my-index', query: { match_all: {} } });

Multiple Nodes Configuration

import { Client } from '@elastic/elasticsearch';
import { ElasticsearchContainer } from '@testcontainers/elasticsearch';

// Create multiple containers for cluster testing
const containers = await Promise.all([
  new ElasticsearchContainer('elasticsearch:8.18.1').start(),
  new ElasticsearchContainer('elasticsearch:8.18.1').start()
]);

// Connect to multiple nodes
const client = new Client({
  nodes: containers.map(c => c.getHttpUrl()),
  auth: {
    username: containers[0].getUsername(),
    password: containers[0].getPassword()
  }
});

Error Handling

Container Startup Errors

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';

try {
  const container = await new ElasticsearchContainer('elasticsearch:8.18.1').start();
  // Use container
} catch (error) {
  if (error instanceof ContainerStartupException) {
    console.error('Container failed to start:', error.message);
    // Check Docker daemon, image availability, system resources
  } else if (error instanceof TimeoutException) {
    console.error('Container startup timeout:', error.message);
    // Increase timeout or check system performance
  } else if (error instanceof DockerException) {
    console.error('Docker error:', error.message);
    // Check Docker daemon status, network connectivity
  } else {
    console.error('Unexpected error:', error);
  }
}

Connection Errors

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Client } from '@elastic/elasticsearch';

const container = await new ElasticsearchContainer('elasticsearch:8.18.1').start();

try {
  const client = new Client({
    node: container.getHttpUrl(),
    auth: {
      username: container.getUsername(),
      password: container.getPassword()
    }
  });

  await client.ping();
} catch (error) {
  if (error instanceof ConnectionException) {
    console.error('Failed to connect to Elasticsearch:', error.message);
    // Verify container is running, check port mapping
  } else if (error instanceof ResponseError) {
    console.error('Elasticsearch error:', error.message);
    // Handle Elasticsearch-specific errors
  }
}

Authentication Errors

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Client } from '@elastic/elasticsearch';

const container = await new ElasticsearchContainer('elasticsearch:8.18.1')
  .withPassword('customPassword')
  .start();

try {
  const client = new Client({
    node: container.getHttpUrl(),
    auth: {
      username: container.getUsername(),
      password: container.getPassword() // Must match password set via withPassword
    }
  });

  await client.ping();
} catch (error) {
  if (error instanceof ResponseError && error.statusCode === 401) {
    console.error('Authentication failed:', error.message);
    // Verify password matches withPassword() setting
  }
}

Container Lifecycle Errors

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';

const container = await new ElasticsearchContainer('elasticsearch:8.18.1').start();

try {
  // Operations on container
  await container.restart();
} catch (error) {
  if (error instanceof Error && error.message.includes('not running')) {
    console.error('Container is not running:', error.message);
    // Container may have stopped unexpectedly
  }
}

Types

All types are provided through TypeScript declarations. The main classes inherit from the testcontainers library:

import type { GenericContainer, AbstractStartedContainer } from 'testcontainers';

// ElasticsearchContainer extends GenericContainer
// StartedElasticsearchContainer extends AbstractStartedContainer

Type Exports:

  • ElasticsearchContainer - Container configuration class
  • StartedElasticsearchContainer - Running container class

Advanced Scenarios

Custom Wait Strategy

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Wait } from 'testcontainers';

// Custom wait strategy with retry logic
const container = await new ElasticsearchContainer('elasticsearch:8.18.1')
  .withWaitStrategy(
    Wait.forHttp('/')
      .forStatusCode(200)
      .withBasicCredentials('elastic', 'changeme')
      .withStartupTimeout(Duration.ofSeconds(180))
  )
  .start();

Container Reuse

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';

// Enable container reuse (requires testcontainers configuration)
const container = await new ElasticsearchContainer('elasticsearch:8.18.1')
  .withReuse(true)
  .start();

// Container will not be removed on stop() if reuse is enabled

Privileged Mode

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';

// Run container in privileged mode (for advanced scenarios)
const container = await new ElasticsearchContainer('elasticsearch:8.18.1')
  .withPrivilegedMode(true)
  .start();

Multiple Containers in Parallel

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';

// Run multiple containers concurrently
const containers = await Promise.all([
  new ElasticsearchContainer('elasticsearch:8.18.1').start(),
  new ElasticsearchContainer('elasticsearch:7.17.28').start(),
  new ElasticsearchContainer('elasticsearch:8.18.1').withPassword('pass1').start()
]);

// Each container gets unique ports
containers.forEach((container, index) => {
  console.log(`Container ${index} URL: ${container.getHttpUrl()}`);
});

// Cleanup all containers
await Promise.all(containers.map(c => c.stop()));

Data Persistence Simulation

import { ElasticsearchContainer } from '@testcontainers/elasticsearch';
import { Client } from '@elastic/elasticsearch';

// Note: Data is ephemeral, but you can simulate persistence by not stopping container
const container = await new ElasticsearchContainer('elasticsearch:8.18.1').start();

const client = new Client({
  node: container.getHttpUrl(),
  auth: { username: container.getUsername(), password: container.getPassword() }
});

// Index data
await client.index({ index: 'test', document: { data: 'value' } });

// Restart loses data
await container.restart();

// Data is gone - need to re-index

Important Notes

  • The container is designed for testing and local development, not production use
  • Data is ephemeral and will be lost when the container stops
  • Each container runs an independent Elasticsearch instance
  • The single-node configuration is optimized for fast startup in test environments
  • Containers are automatically cleaned up when using the await using syntax (TypeScript 5.2+)
  • For manual cleanup, call await container.stop() when done
  • Container ports are randomly assigned to avoid conflicts
  • Multiple containers can run simultaneously (each gets unique ports)
  • Container startup time varies based on system resources and network speed
  • Elasticsearch 8.x requires authentication (handled automatically by container)
  • Elasticsearch 7.x may have different authentication requirements
  • JVM memory settings can be overridden via environment variables
  • Container logs are available via container.logs() method
  • Container execution commands can be run via container.exec()