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.
Required Dependencies:
@testcontainers/elasticsearch (this package)testcontainers core package is required@elastic/elasticsearch client library (for interacting with Elasticsearch)Default Behaviors:
'changeme' (configurable via withPassword())'elastic' (fixed, not configurable)9200 (exposed and mapped to random host port)single-node (Elasticsearch runs in single-node mode)xpack.security.http.ssl.enabled=false)2GB (set via -Xmx2G)120 secondsawait using syntax)Threading Model:
ElasticsearchContainer instances are not thread-safe (create per test)StartedElasticsearchContainer instances are thread-safe for read operations (getPort, getHttpUrl, etc.)start(), stop(), restart()) are not thread-safeLifecycle:
await container.start())await using syntax (TypeScript 5.2+)await container.stop() when doneawait container.restart() (data will be lost)await usingExceptions:
ContainerStartupException - Container failed to start within timeoutDockerException - Docker daemon not available or image pull failedConnectionException - Unable to connect to started containerTimeoutException - Startup timeout exceeded (120 seconds default)ElasticsearchException - Elasticsearch-specific errors (from client library)Edge Cases:
getPort() to get actual port)withStartupTimeout())start() is calledwithNetworkMode()withCopyContentToContainer() for persistent setup)@testcontainers/elasticsearchnpm install @testcontainers/elasticsearch --save-devimport { ElasticsearchContainer, StartedElasticsearchContainer } from '@testcontainers/elasticsearch';For CommonJS:
const { ElasticsearchContainer, StartedElasticsearchContainer } = require('@testcontainers/elasticsearch');The module provides two main classes:
GenericContainer from testcontainers. Provides Elasticsearch-specific defaults and configuration methods.The container is pre-configured with:
discovery.type=single-node)xpack.security.http.ssl.enabled=false)elastic/**
* 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 portswithEnvironment(key: string, value: string) - Set environment variableswithEnvironment(env: Record<string, string>) - Set multiple environment variableswithWaitStrategy(strategy: WaitStrategy) - Configure custom wait strategywithStartupTimeout(timeout: Duration) - Set startup timeout (default: 120 seconds)withCopyContentToContainer(content: Transferable) - Copy files/content to containerwithNetworkMode(mode: string) - Configure network mode (e.g., 'host', 'bridge')withCommand(command: string[]) - Override container commandwithNetwork(network: Network) - Attach to specific networkwithReuse(reuse: boolean) - Enable container reusewithPrivilegedMode(privileged: boolean) - Run in privileged modewithExtraHost(host: string, ipAddress: string) - Add extra host entrywithLogConsumer(consumer: Consumer<OutputFrame>) - Configure log consumption/**
* 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 portgetName() - 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 containergetNetworkNames() - Get network names container is attached togetNetworkId(networkName: string) - Get network ID for specific networkimport { 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();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()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'
}
});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 });
});
});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 });
});
});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();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);
});
});
}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();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();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();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();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();The module supports both major Elasticsearch versions:
elasticsearch:7.17.28)elasticsearch:8.18.1)Images can be found in the Elasticsearch Docker Hub registry.
Version Compatibility Notes:
@elastic/elasticsearch client version)The ElasticsearchContainer applies the following default configuration:
| Setting | Value | Description |
|---|---|---|
| HTTP Port | 9200 | Exposed and mapped to random host port |
| Username | elastic | Fixed authentication username |
| Password | changeme | Default password (configurable via withPassword()) |
| Discovery Type | single-node | Elasticsearch runs in single-node mode |
| SSL | Disabled | HTTP SSL is disabled for testing (xpack.security.http.ssl.enabled=false) |
| JVM Memory | 2GB | Maximum heap size set to -Xmx2G |
| Startup Timeout | 120 seconds | Maximum time to wait for container to be ready |
| Wait Strategy | HTTP endpoint check | Waits for HTTP endpoint to respond with basic auth |
| Data Persistence | Ephemeral | Data is lost when container stops |
This module is designed to work seamlessly with Elasticsearch client libraries:
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: {} } });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()
}
});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);
}
}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
}
}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
}
}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
}
}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 AbstractStartedContainerType Exports:
ElasticsearchContainer - Container configuration classStartedElasticsearchContainer - Running container classimport { 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();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 enabledimport { ElasticsearchContainer } from '@testcontainers/elasticsearch';
// Run container in privileged mode (for advanced scenarios)
const container = await new ElasticsearchContainer('elasticsearch:8.18.1')
.withPrivilegedMode(true)
.start();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()));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-indexawait using syntax (TypeScript 5.2+)await container.stop() when donecontainer.logs() methodcontainer.exec()