or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

index.mddocs/

ClickHouse Testcontainers

ClickHouse module for Testcontainers that enables developers to programmatically create, configure, and manage ClickHouse Docker containers for testing purposes. The module offers a fluent API for container configuration, exposes both native and HTTP interfaces, and integrates seamlessly with the official @clickhouse/client library.

Key Information for Agents

Required Dependencies:

  • @testcontainers/clickhouse (this package)
  • @testcontainers/core is required (provided transitively)
  • testcontainers package is required (provided transitively)
  • Docker must be installed and running
  • @clickhouse/client is recommended for connecting to the container (not required by testcontainers itself)

Default Behaviors:

  • Default credentials: username="test", password="test", database="test"
  • Default image: Must be specified in constructor (no default)
  • Exposed ports: 9000 (native protocol), 8123 (HTTP interface)
  • Health check: HTTP endpoint at port 8123, expects "Ok.\n" response
  • Startup timeout: 120 seconds
  • Ulimits: nofile hard/soft set to 262144 to prevent "Too many open files" errors
  • Port mapping: Random available ports on host (use getPort()/getHttpPort() to retrieve)
  • Container reuse: Not enabled by default (new container per test)
  • Network isolation: Each container runs in isolated network by default

Threading Model:

  • Container operations are asynchronous (return Promises)
  • start() method is async and must be awaited
  • stop() method is async and must be awaited
  • Container instances are not thread-safe for concurrent modification
  • Multiple containers can run concurrently in separate instances
  • Client connections from @clickhouse/client are independent of container lifecycle

Lifecycle:

  • Container must be started with await container.start() before use
  • Container automatically waits for health check to pass before start() resolves
  • Container must be stopped with await container.stop() for cleanup
  • Always stop containers in finally blocks or use test framework cleanup hooks
  • Container stops automatically when Node.js process exits (if not explicitly stopped)
  • Failed containers may leave Docker resources; always call stop() even on errors

Exceptions:

  • ContainerStartException - Container failed to start (timeout, image pull failure, etc.)
  • PortBindingException - Port binding conflicts
  • DockerNotAvailableException - Docker daemon not accessible
  • ImagePullException - Failed to pull Docker image
  • Network errors from @clickhouse/client - Connection failures, query errors
  • TimeoutError - Health check timeout (container started but not ready)

Edge Cases:

  • Port conflicts: Testcontainers automatically finds available ports, but conflicts can occur in CI/CD
  • Image pull failures: Network issues or invalid image tags cause startup failures
  • Health check timeouts: Slow container startup may exceed 120s timeout (adjust with withStartupTimeout())
  • Multiple containers: Each container gets unique ports; coordinate if testing multi-container scenarios
  • Container reuse: Enable with withReuse(true) to reuse containers across test runs (requires testcontainers configuration)
  • File descriptor limits: Default ulimits prevent issues, but may need adjustment for high-load tests
  • Network isolation: Containers cannot communicate by default; use withNetworkMode() for multi-container tests
  • Resource cleanup: Always stop containers even if tests fail; use try/finally or test framework hooks
  • Concurrent tests: Each test should use separate container instances to avoid port conflicts
  • CI/CD environments: May require Docker-in-Docker or remote Docker configuration

Package Information

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

For production dependencies (if needed):

npm install @testcontainers/clickhouse

Core Imports

import { ClickHouseContainer, StartedClickHouseContainer } from "@testcontainers/clickhouse";

For CommonJS:

const { ClickHouseContainer, StartedClickHouseContainer } = require("@testcontainers/clickhouse");

Note: StartedClickHouseContainer is typically obtained by calling start() on a ClickHouseContainer instance, but the type is exported for type annotations.

Basic Usage

Minimal Example

import { ClickHouseContainer } from "@testcontainers/clickhouse";
import { createClient } from "@clickhouse/client";

// Start a ClickHouse container
const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();

// Connect using the built-in client options
const client = createClient(container.getClientOptions());

// Execute queries
const result = await client.query({
  query: "SELECT 1 AS value",
  format: "JSON",
});

// Clean up
await client.close();
await container.stop();

With Error Handling

import { ClickHouseContainer } from "@testcontainers/clickhouse";
import { createClient } from "@clickhouse/client";

let container: StartedClickHouseContainer | null = null;
let client: any = null;

try {
  container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();
  client = createClient(container.getClientOptions());
  
  const result = await client.query({
    query: "SELECT 1 AS value",
    format: "JSON",
  });
  
  console.log(result.json());
} catch (error) {
  console.error("Test failed:", error);
  throw error;
} finally {
  if (client) {
    await client.close();
  }
  if (container) {
    await container.stop();
  }
}

With Custom Configuration

import { ClickHouseContainer } from "@testcontainers/clickhouse";
import { createClient } from "@clickhouse/client";

const container = await new ClickHouseContainer("clickhouse/clickhouse-server:23.8")
  .withUsername("myuser")
  .withPassword("mypassword")
  .withDatabase("mydb")
  .start();

const client = createClient(container.getClientOptions());

// Use the container...
await client.close();
await container.stop();

Architecture

The module provides two main classes:

  • ClickHouseContainer: Builder class for configuring and starting ClickHouse containers. Extends Testcontainers' GenericContainer to provide ClickHouse-specific configuration methods.
  • StartedClickHouseContainer: Represents a running container instance. Provides methods to retrieve connection details, port mappings, and credentials.

The module automatically:

  • Exposes both native protocol (port 9000) and HTTP interface (port 8123)
  • Configures health checks via HTTP endpoint polling
  • Sets appropriate ulimits to prevent file descriptor exhaustion under load
  • Handles container lifecycle management with proper startup timeouts

Capabilities

Container Configuration and Startup

Configure and start ClickHouse containers with custom credentials and database settings.

/**
 * Main container class for creating and configuring ClickHouse test containers.
 * Extends GenericContainer from the testcontainers package.
 */
class ClickHouseContainer extends GenericContainer {
  /**
   * Creates a new ClickHouse container instance
   * @param image - Docker image name (e.g., "clickhouse/clickhouse-server:latest")
   */
  constructor(image: string);

  /**
   * Sets the database name to create and use
   * @param database - Database name (default: "test")
   * @returns This container instance for method chaining
   */
  withDatabase(database: string): this;

  /**
   * Sets the username for authentication
   * @param username - Username (default: "test")
   * @returns This container instance for method chaining
   */
  withUsername(username: string): this;

  /**
   * Sets the password for authentication
   * @param password - Password (default: "test")
   * @returns This container instance for method chaining
   */
  withPassword(password: string): this;

  /**
   * Starts the container and returns a started instance
   * @returns Promise resolving to a StartedClickHouseContainer
   * @throws ContainerStartException if container fails to start
   * @throws TimeoutError if health check times out
   */
  start(): Promise<StartedClickHouseContainer>;
}

Usage Examples:

import { ClickHouseContainer } from "@testcontainers/clickhouse";

// Basic usage with defaults (username: "test", password: "test", database: "test")
const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();

// Custom configuration
const container = await new ClickHouseContainer("clickhouse/clickhouse-server:23.8")
  .withUsername("myuser")
  .withPassword("mypassword")
  .withDatabase("mydb")
  .start();

// With specific image version
const container = await new ClickHouseContainer("clickhouse/clickhouse-server:23.8.23.12.1")
  .withDatabase("production_test")
  .start();

Connection Information and Client Integration

Retrieve connection details and configuration objects for connecting to the running ClickHouse container.

/**
 * Represents a running ClickHouse container with methods to retrieve connection details.
 * Extends AbstractStartedContainer from the testcontainers package.
 */
class StartedClickHouseContainer extends AbstractStartedContainer {
  /**
   * Returns the mapped host port for the native ClickHouse protocol
   * @returns Port number for native protocol (internal port 9000)
   */
  getPort(): number;

  /**
   * Returns the mapped host port for the HTTP interface
   * @returns Port number for HTTP interface (internal port 8123)
   */
  getHttpPort(): number;

  /**
   * Returns the configured username
   * @returns Username string
   */
  getUsername(): string;

  /**
   * Returns the configured password
   * @returns Password string
   */
  getPassword(): string;

  /**
   * Returns the configured database name
   * @returns Database name string
   */
  getDatabase(): string;

  /**
   * Gets the base HTTP URL (protocol, host and mapped port) for the ClickHouse container's HTTP interface
   * @returns HTTP URL string (e.g., "http://localhost:32768")
   */
  getHttpUrl(): string;

  /**
   * Gets configuration options suitable for passing directly to createClient() from @clickhouse/client
   * Uses the HTTP interface. This is the recommended method for connecting.
   * @returns Configuration object with url, username, password, and database
   */
  getClientOptions(): ClientOptions;

  /**
   * Gets a ClickHouse connection URL for the HTTP interface with embedded credentials
   * Format: http://username:password@hostname:port/database
   * @returns Connection URL string
   */
  getConnectionUrl(): string;

  /**
   * Stops the container and cleans up resources
   * @returns Promise that resolves when container is stopped
   */
  stop(): Promise<void>;

  /**
   * Restarts the container
   * @returns Promise that resolves when container is restarted
   */
  restart(): Promise<void>;

  /**
   * Gets container logs
   * @returns Promise resolving to log stream or string
   */
  logs(): Promise<string>;

  /**
   * Executes a command in the running container
   * @param command - Command to execute
   * @returns Promise resolving to command execution result
   */
  exec(command: string[]): Promise<ExecResult>;
}

Usage Examples:

import { ClickHouseContainer } from "@testcontainers/clickhouse";
import { createClient } from "@clickhouse/client";

const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withUsername("customuser")
  .withPassword("custompass")
  .withDatabase("testdb")
  .start();

// Method 1: Using getClientOptions() (recommended)
const client1 = createClient(container.getClientOptions());

// Method 2: Using getConnectionUrl()
const client2 = createClient({
  url: container.getConnectionUrl(),
});

// Method 3: Using individual getters
const client3 = createClient({
  url: container.getHttpUrl(),
  username: container.getUsername(),
  password: container.getPassword(),
  database: container.getDatabase(),
});

// Get port information
console.log(`Native protocol port: ${container.getPort()}`);
console.log(`HTTP interface port: ${container.getHttpPort()}`);

// Execute queries
const result = await client1.query({
  query: "SELECT version()",
  format: "JSON",
});

await client1.close();
await container.stop();

Advanced Configuration

The container extends GenericContainer, providing access to all Testcontainers configuration methods.

Environment Variables:

const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withEnvironment("CLICKHOUSE_DB", "customdb")
  .withEnvironment("CLICKHOUSE_USER", "customuser")
  .withEnvironment("CLICKHOUSE_PASSWORD", "custompass")
  .withEnvironment("CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT", "1")
  .start();

Custom Ports:

const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withExposedPorts(9000, 8123, 9009) // Native, HTTP, Inter-Server
  .start();

// Get specific port mappings
const nativePort = container.getMappedPort(9000);
const httpPort = container.getMappedPort(8123);
const interServerPort = container.getMappedPort(9009);

Startup Timeout:

const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withStartupTimeout(Duration.ofSeconds(300)) // 5 minutes for slow environments
  .start();

Container Reuse:

// Enable container reuse (requires testcontainers configuration)
const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withReuse(true)
  .start();

File System Operations:

// Copy files into container
const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withCopyFilesToContainer([
    {
      source: "./config.xml",
      target: "/etc/clickhouse-server/config.d/custom-config.xml",
    },
  ])
  .start();

// Bind mount host directory
const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withBindMounts([
    {
      source: "./data",
      target: "/var/lib/clickhouse/data",
    },
  ])
  .start();

Network Configuration:

// Use host network mode
const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withNetworkMode("host")
  .start();

// Create custom network for multi-container tests
import { Network } from "testcontainers";

const network = await Network.newNetwork().start();
const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withNetwork(network)
  .start();

Resource Limits:

const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withMemory(2 * 1024 * 1024 * 1024) // 2GB
  .withCpuCount(2)
  .start();

Lifecycle Management

Proper Cleanup Pattern:

import { ClickHouseContainer } from "@testcontainers/clickhouse";

let container: StartedClickHouseContainer | null = null;

try {
  container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();
  // Use container...
} finally {
  if (container) {
    await container.stop();
  }
}

With Test Framework Integration (Jest):

import { ClickHouseContainer } from "@testcontainers/clickhouse";

describe("ClickHouse Tests", () => {
  let container: StartedClickHouseContainer;

  beforeAll(async () => {
    container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();
  });

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

  it("should execute queries", async () => {
    const client = createClient(container.getClientOptions());
    // Test code...
    await client.close();
  });
});

With Test Framework Integration (Mocha):

import { ClickHouseContainer } from "@testcontainers/clickhouse";

describe("ClickHouse Tests", function () {
  let container: StartedClickHouseContainer;

  before(async function () {
    container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();
  });

  after(async function () {
    await container.stop();
  });

  it("should execute queries", async function () {
    const client = createClient(container.getClientOptions());
    // Test code...
    await client.close();
  });
});

Container Restart:

const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();

// Use container...
await container.restart(); // Restart the container
// Container is ready again after restart

Error Handling

Startup Failures:

import { ClickHouseContainer } from "@testcontainers/clickhouse";

try {
  const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();
} catch (error) {
  if (error instanceof ContainerStartException) {
    console.error("Container failed to start:", error.message);
    // Check Docker daemon, image availability, port conflicts
  } else if (error instanceof TimeoutError) {
    console.error("Container health check timed out");
    // Increase startup timeout or check container logs
  } else {
    console.error("Unexpected error:", error);
  }
  throw error;
}

Connection Failures:

import { createClient } from "@clickhouse/client";

const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();

try {
  const client = createClient(container.getClientOptions());
  
  // Wait for container to be fully ready
  await new Promise((resolve) => setTimeout(resolve, 1000));
  
  const result = await client.query({
    query: "SELECT 1",
    format: "JSON",
  });
} catch (error) {
  if (error.code === "ECONNREFUSED") {
    console.error("Connection refused - container may not be ready");
  } else if (error.code === "ETIMEDOUT") {
    console.error("Connection timeout");
  } else {
    console.error("Query error:", error);
  }
  throw error;
}

Query Errors:

try {
  const result = await client.query({
    query: "SELECT * FROM non_existent_table",
    format: "JSON",
  });
} catch (error) {
  if (error.code === 60) {
    // ClickHouse error code 60: UNKNOWN_TABLE
    console.error("Table does not exist");
  } else {
    console.error("Query failed:", error.message);
  }
}

Multiple Containers

Parallel Test Execution:

import { ClickHouseContainer } from "@testcontainers/clickhouse";

// Each test gets its own container
const test1 = async () => {
  const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();
  // Test code...
  await container.stop();
};

const test2 = async () => {
  const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();
  // Test code...
  await container.stop();
};

// Run in parallel - each gets unique ports
await Promise.all([test1(), test2()]);

Multi-Container Scenarios:

import { ClickHouseContainer } from "@testcontainers/clickhouse";
import { Network } from "testcontainers";

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

try {
  // Create multiple containers on same network
  const container1 = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
    .withNetwork(network)
    .withNetworkAliases("clickhouse1")
    .start();

  const container2 = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
    .withNetwork(network)
    .withNetworkAliases("clickhouse2")
    .start();

  // Containers can communicate using network aliases
  // Use container1.getHost() and container2.getHost() for connections
} finally {
  await container1?.stop();
  await container2?.stop();
  await network.stop();
}

Health Checks and Readiness

Custom Health Check:

import { ClickHouseContainer } from "@testcontainers/clickhouse";
import { Wait } from "testcontainers";

const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withWaitStrategy(
    Wait.forHttp("/ping", 8123)
      .forStatusCode(200)
      .withStartupTimeout(Duration.ofSeconds(300))
  )
  .start();

Verifying Container Readiness:

const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();

// Container is ready when start() resolves
// Additional verification if needed:
const client = createClient(container.getClientOptions());

// Simple readiness check
try {
  await client.ping();
  console.log("Container is ready");
} catch (error) {
  console.error("Container not ready:", error);
}

Performance Considerations

Connection Pooling:

import { createClient } from "@clickhouse/client";

const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest").start();

// Create client with connection pooling
const client = createClient({
  ...container.getClientOptions(),
  max_open_connections: 10,
  request_timeout: 30000,
});

// Reuse client across multiple queries
for (let i = 0; i < 100; i++) {
  await client.query({
    query: `INSERT INTO test_table VALUES (${i})`,
  });
}

await client.close();
await container.stop();

Batch Operations:

const client = createClient(container.getClientOptions());

// Batch insert
const values = Array.from({ length: 1000 }, (_, i) => `(${i}, 'value${i}')`).join(",");
await client.exec({
  query: `INSERT INTO test_table VALUES ${values}`,
});

// Or use insert method for structured data
await client.insert({
  table: "test_table",
  values: Array.from({ length: 1000 }, (_, i) => ({ id: i, value: `value${i}` })),
  format: "JSONEachRow",
});

Resource Management:

// Use ulimits for high-load tests (already set by default, but can be customized)
const container = await new ClickHouseContainer("clickhouse/clickhouse-server:latest")
  .withCreateContainerCmdModifier((cmd) => {
    cmd.getHostConfig().withUlimits([
      {
        name: "nofile",
        soft: 262144,
        hard: 262144,
      },
    ]);
  })
  .start();

Types

/**
 * Configuration options returned by getClientOptions() for use with @clickhouse/client
 */
interface ClientOptions {
  /** Base HTTP URL for the ClickHouse container */
  url?: string;
  /** Username for authentication */
  username: string;
  /** Password for authentication */
  password: string;
  /** Database name to use */
  database: string;
}

Inherited Functionality

Both ClickHouseContainer and StartedClickHouseContainer inherit from Testcontainers base classes, providing additional functionality:

From GenericContainer (available on ClickHouseContainer):

  • withEnvironment(key, value): Set environment variables
  • withExposedPorts(...ports): Expose additional ports
  • withCopyFilesToContainer(files): Copy files into the container
  • withBindMounts(mounts): Mount host directories
  • withNetworkMode(mode): Configure network mode
  • withNetwork(network): Attach to a network
  • withNetworkAliases(...aliases): Set network aliases
  • withStartupTimeout(duration): Configure startup timeout
  • withWaitStrategy(strategy): Configure wait strategy
  • withReuse(reuse): Enable container reuse
  • withMemory(bytes): Set memory limit
  • withCpuCount(count): Set CPU count
  • withCreateContainerCmdModifier(modifier): Customize container creation
  • And many more - see Testcontainers documentation

From AbstractStartedContainer (available on StartedClickHouseContainer):

  • stop(): Stop the container
  • restart(): Restart the container
  • logs(): Get container logs
  • exec(command): Execute commands in the container
  • getHost(): Get the container host
  • getMappedPort(internalPort): Get mapped port for any exposed port
  • getContainerId(): Get Docker container ID
  • And many more - see Testcontainers documentation

Default Configuration

The module applies the following default configuration:

  • Default Credentials: username="test", password="test", database="test"
  • Exposed Ports: 9000 (native protocol), 8123 (HTTP interface)
  • Health Check: HTTP endpoint at port 8123, expects "Ok.\n" response
  • Startup Timeout: 120 seconds
  • Ulimits: nofile hard/soft set to 262144 to prevent "Too many open files" errors

These defaults can be overridden using the withDatabase(), withUsername(), and withPassword() methods, or by using inherited Testcontainers methods for more advanced configuration.

Troubleshooting

Container Won't Start:

  • Verify Docker is running: docker ps
  • Check Docker daemon accessibility
  • Verify image name and tag are correct
  • Check for port conflicts (though testcontainers handles this automatically)
  • Review container logs: await container.logs()

Connection Refused:

  • Ensure await container.start() completed successfully
  • Wait a moment after start() resolves for container to be fully ready
  • Verify using container.getHttpUrl() and container.getPort()
  • Check container logs for errors

Health Check Timeout:

  • Increase startup timeout: .withStartupTimeout(Duration.ofSeconds(300))
  • Check container logs for startup errors
  • Verify image is compatible with testcontainers
  • Check system resources (memory, CPU)

Port Conflicts in CI/CD:

  • Testcontainers automatically finds available ports
  • If issues persist, configure Docker socket or remote Docker
  • Consider using container reuse for faster test execution

Resource Exhaustion:

  • Ensure containers are stopped after tests
  • Use try/finally blocks or test framework hooks
  • Check Docker for orphaned containers: docker ps -a
  • Clean up: docker container prune

Image Pull Failures:

  • Check network connectivity
  • Verify image registry accessibility
  • Use specific image tags instead of "latest"
  • Pre-pull images: docker pull clickhouse/clickhouse-server:latest