or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

index.mddocs/

Redis Module for Testcontainers

Redis module for Testcontainers that enables developers to programmatically create, configure, and manage Redis Docker containers for testing purposes. The module offers a fluent API for container configuration, supports authentication, data persistence, initial data loading, and provides seamless integration with Redis client libraries. It supports both standard Redis images and Redis Stack images (with extended features like JSON, Search, and Time Series).

Key Information for Agents

Required Dependencies:

  • @testcontainers/redis (this package)
  • @testcontainers/core is required (provided transitively)
  • testcontainers package is required (provided transitively)
  • Docker must be installed and running
  • Redis client library (e.g., redis npm package) is recommended for connecting to the container (not required by testcontainers itself)

Default Behaviors:

  • Default port: 6379 (Redis standard port, automatically exposed and mapped to random host port)
  • Default image: Must be specified in constructor (no default, typically redis:7 or redis/redis-stack:latest)
  • Default password: No password required (empty string returned by getPassword())
  • Default persistence: No persistence (data lost on container restart unless withPersistence() used)
  • Default initial data: No initial data loaded (empty container)
  • Health check: Redis PING command to verify container readiness
  • Startup timeout: Inherited from GenericContainer (typically 60 seconds)
  • Port mapping: Random available ports on host (use getPort() to retrieve)
  • Container reuse: Not enabled by default (new container per test)
  • Network isolation: Each container runs in isolated network by default
  • Redis Stack detection: Automatically detected by image name containing "redis-stack" or "redis/redis-stack"
  • Connection URL format: redis://:password@host:port (password omitted if empty)

Threading Model:

  • Container operations are asynchronous (return Promises)
  • start() method is async and must be awaited
  • stop() and restart() methods are async and must be awaited
  • Container instances are not thread-safe for concurrent modification
  • Multiple containers can run concurrently in separate instances (each gets unique ports)
  • Client connections from Redis client libraries are independent of container lifecycle
  • executeCliCmd() is async and must be awaited
  • Container lifecycle methods should not be called concurrently on the same instance

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
  • Use await using syntax for automatic disposal (recommended pattern)
  • restart() maintains configuration and data (if persistence enabled)
  • withInitialData() loads data during container startup (before start() resolves)
  • withPersistence() requires directory to exist or be created before container start

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
  • TimeoutError - Health check timeout (container started but not ready)
  • Network errors from Redis clients - Connection failures, authentication errors
  • Error from executeCliCmd() - Redis CLI command execution failures (non-zero exit code)
  • RedisError from Redis clients - Redis protocol errors, command errors
  • ConnectionError from Redis clients - Connection refused, timeout errors

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 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)
  • Network isolation: Containers cannot communicate by default; use withNetwork() and withNetworkAliases() 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
  • Password authentication: If password set, all Redis client connections must include password; getConnectionUrl() includes password automatically
  • Persistence directory: Must exist before container start; use fs.mkdirSync() or fs.mkdtempSync() to create
  • Initial data format: Must be valid Redis command script format (one command per line); file must exist and be readable
  • Redis Stack features: JSON, Search, Time Series modules only available with redis/redis-stack images
  • Connection URL encoding: Password in URL is automatically encoded; special characters handled correctly
  • Container restart: Data persists only if withPersistence() was used; otherwise data is lost
  • executeCliCmd() output: Returns stdout as string; stderr included if command fails
  • Empty password: getPassword() returns empty string; getConnectionUrl() omits password section
  • Client connection timing: Wait for start() to resolve before connecting clients; container may need additional time to be fully ready
  • File system permissions: Persistence directory must have appropriate permissions for Docker to mount
  • Initial data loading: Happens during startup; large files may slow container start
  • Redis version compatibility: Different Redis versions may have different command syntax; verify image version matches requirements

Package Information

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

For production dependencies (if needed):

npm install @testcontainers/redis

Core Imports

import { RedisContainer, StartedRedisContainer } from "@testcontainers/redis";

For CommonJS:

const { RedisContainer, StartedRedisContainer } = require("@testcontainers/redis");

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

Basic Usage

Minimal Example

import { RedisContainer } from "@testcontainers/redis";
import { createClient } from "redis";

// Start a Redis container
const container = await new RedisContainer("redis:7").start();

// Connect using the connection URL
const client = createClient({ url: container.getConnectionUrl() });
await client.connect();

// Execute operations
await client.set("key", "value");
const value = await client.get("key");

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

With Error Handling

import { RedisContainer } from "@testcontainers/redis";
import { createClient } from "redis";

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

try {
  container = await new RedisContainer("redis:7").start();
  client = createClient({ url: container.getConnectionUrl() });
  await client.connect();
  
  await client.set("key", "value");
  const value = await client.get("key");
  console.log(value);
} catch (error) {
  console.error("Test failed:", error);
  throw error;
} finally {
  if (client) {
    client.destroy();
  }
  if (container) {
    await container.stop();
  }
}

With Automatic Disposal

import { RedisContainer } from "@testcontainers/redis";
import { createClient } from "redis";

await using container = await new RedisContainer("redis:7").start();

const client = createClient({ url: container.getConnectionUrl() });
await client.connect();

// Use Redis
await client.set("key", "value");
const value = await client.get("key");

client.destroy();
// Container automatically disposed at end of 'await using' block

With Password Authentication

import { RedisContainer } from "@testcontainers/redis";
import { createClient } from "redis";

const container = await new RedisContainer("redis:7")
  .withPassword("mypassword")
  .start();

const client = createClient({ url: container.getConnectionUrl() });
await client.connect();

// Use Redis with authentication
await client.set("key", "value");

Architecture

The module provides two main classes:

  • RedisContainer: Builder class for configuring and starting Redis containers. Extends Testcontainers' GenericContainer to provide Redis-specific configuration methods.
  • StartedRedisContainer: Represents a running container instance. Provides methods to retrieve connection details, execute Redis CLI commands, and control the running container.

The module automatically:

  • Exposes Redis port 6379
  • Configures health checks via Redis PING command
  • Handles container lifecycle management with proper startup timeouts
  • Detects Redis Stack images and configures appropriately
  • Generates properly formatted connection URLs

Capabilities

Container Configuration and Startup

Configure and start Redis containers with authentication, persistence, and initial data.

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

  /**
   * Sets the password for Redis authentication
   * @param password - Password to require for Redis connections
   * @returns This container instance for method chaining
   */
  withPassword(password: string): this;

  /**
   * Enables data persistence using a local directory path
   * Mounts the directory to Redis data directory in container
   * @param sourcePath - Local directory path to mount for persistence
   * @returns This container instance for method chaining
   */
  withPersistence(sourcePath: string): this;

  /**
   * Loads initial data from a Redis command script file
   * Data must be in Redis import script format (one command per line)
   * @param importScriptFile - Path to Redis command script file
   * @returns This container instance for method chaining
   * @see https://developer.redis.com/explore/import/
   */
  withInitialData(importScriptFile: string): this;

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

Usage Examples:

import { RedisContainer } from "@testcontainers/redis";

// Basic usage without password
const container = await new RedisContainer("redis:7").start();

// With password authentication
const secureContainer = await new RedisContainer("redis:7")
  .withPassword("mypassword")
  .start();

// With persistence
import fs from "fs";
const dataDir = fs.mkdtempSync("redis-data-");
const persistentContainer = await new RedisContainer("redis:7")
  .withPassword("mypassword")
  .withPersistence(dataDir)
  .start();

// With initial data
const preloadedContainer = await new RedisContainer("redis:7")
  .withPassword("test")
  .withInitialData("./seed-data.redis")
  .start();

// Redis Stack (for extended features like JSON, Search, Time Series)
const stackContainer = await new RedisContainer("redis/redis-stack:latest")
  .withPassword("mypassword")
  .start();

Connection Information and Container Control

Retrieve connection details and control the running Redis container.

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

  /**
   * Returns the configured password
   * @returns Password string, or empty string if no password set
   */
  getPassword(): string;

  /**
   * Returns full Redis connection URL for client libraries
   * Format: redis://:password@host:port (password omitted if empty)
   * @returns Redis connection URL string
   */
  getConnectionUrl(): string;

  /**
   * Returns the container host address
   * @returns Host address string (typically "localhost")
   */
  getHost(): string;

  /**
   * Executes Redis CLI commands directly in the container
   * @param cmd - Redis CLI command to execute (e.g., "info", "keys *")
   * @param additionalFlags - Optional additional flags for redis-cli (e.g., ["-a", "password"])
   * @returns Promise resolving to command output string
   * @throws Error if command fails (non-zero exit code)
   */
  executeCliCmd(cmd: string, additionalFlags?: string[]): Promise<string>;

  /**
   * Restarts the container while maintaining configuration
   * Data persists only if withPersistence() was used
   * @param options - Optional restart options
   */
  restart(options?: Partial<RestartOptions>): Promise<void>;

  /**
   * Stops the container
   * @param options - Optional stop options
   * @returns Promise resolving to stopped container
   */
  stop(options?: Partial<StopOptions>): Promise<StoppedTestContainer>;

  /**
   * Executes a command inside the container
   * @param command - Command string or array of command parts
   * @param opts - Optional execution options
   * @returns Promise resolving to execution result with exitCode and output
   */
  exec(command: string | string[], opts?: Partial<ExecOptions>): Promise<ExecResult>;

  /**
   * Retrieves container logs
   * @param opts - Optional log options (since timestamp, tail count)
   * @returns Promise resolving to readable stream of logs
   */
  logs(opts?: { since?: number; tail?: number }): Promise<Readable>;

  /**
   * Async disposal for 'await using' syntax
   * Automatically stops and removes the container
   */
  [Symbol.asyncDispose](): Promise<void>;

  /**
   * Returns the container hostname
   * @returns Container hostname string
   */
  getHostname(): string;

  /**
   * Returns the first mapped port
   * @returns First mapped port number
   */
  getFirstMappedPort(): number;

  /**
   * Returns the host-mapped port for a given container port
   * @param port - Container port number
   * @param protocol - Optional protocol ('tcp' or 'udp'), defaults to 'tcp'
   * @returns Mapped host port number
   */
  getMappedPort(port: number, protocol?: string): number;

  /**
   * Returns the host-mapped port for a given container port with protocol
   * @param portWithProtocol - Port with protocol string (e.g., "6379/tcp")
   * @returns Mapped host port number
   */
  getMappedPort(portWithProtocol: `${number}/${"tcp" | "udp"}`): number;

  /**
   * Returns the container name
   * @returns Container name string
   */
  getName(): string;

  /**
   * Returns the container labels
   * @returns Container labels as key-value pairs
   */
  getLabels(): Labels;

  /**
   * Returns the container ID
   * @returns Container ID string
   */
  getId(): string;

  /**
   * Returns array of network names the container is connected to
   * @returns Array of network name strings
   */
  getNetworkNames(): string[];

  /**
   * Returns the network ID for a given network name
   * @param networkName - Name of the network
   * @returns Network ID string
   */
  getNetworkId(networkName: string): string;

  /**
   * Returns the container IP address for a given network
   * @param networkName - Name of the network
   * @returns IP address string
   */
  getIpAddress(networkName: string): string;

  /**
   * Copies files from host to container
   * @param filesToCopy - Array of file copy specifications
   */
  copyFilesToContainer(filesToCopy: FileToCopy[]): Promise<void>;

  /**
   * Copies directories from host to container
   * @param directoriesToCopy - Array of directory copy specifications
   */
  copyDirectoriesToContainer(directoriesToCopy: DirectoryToCopy[]): Promise<void>;

  /**
   * Copies content (string/buffer/stream) to container
   * @param contentsToCopy - Array of content copy specifications
   */
  copyContentToContainer(contentsToCopy: ContentToCopy[]): Promise<void>;

  /**
   * Copies tar archive to container
   * @param tar - Readable stream containing tar archive
   * @param target - Target path in container (defaults to "/")
   */
  copyArchiveToContainer(tar: Readable, target?: string): Promise<void>;

  /**
   * Copies file or directory from container as tar archive
   * @param path - Path in container to copy
   * @returns Readable stream of tar archive
   */
  copyArchiveFromContainer(path: string): Promise<NodeJS.ReadableStream>;

  /**
   * Commits the container to a new image
   * @param options - Commit options
   * @returns Promise resolving to new image ID
   */
  commit(options: CommitOptions): Promise<string>;
}

Usage Examples:

import { RedisContainer } from "@testcontainers/redis";
import { createClient } from "redis";

await using container = await new RedisContainer("redis:7")
  .withPassword("test")
  .start();

// Get connection details
const host = container.getHost();
const port = container.getPort();
const password = container.getPassword();
const url = container.getConnectionUrl();
console.log(`Redis available at ${url}`);

// Connect client
const client = createClient({ url: container.getConnectionUrl() });
await client.connect();

// Use Redis
await client.set("key", "value");
const value = await client.get("key");

// Execute Redis CLI commands directly
const info = await container.executeCliCmd("info", ["clients"]);
console.log(info); // Output: connected_clients:1\n...

// Restart with persistent data
await container.restart();
const persistedValue = await client.get("key"); // Still accessible if persistence enabled

client.destroy();
// Container automatically disposed at end of 'await using' block

Advanced Configuration

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

Environment Variables:

const container = await new RedisContainer("redis:7")
  .withEnvironment("REDIS_PASSWORD", "custompass")
  .withEnvironment("REDIS_ARGS", "--loglevel verbose")
  .start();

Custom Ports:

const container = await new RedisContainer("redis:7")
  .withExposedPorts(6379, 6380) // Redis, custom port
  .start();

// Get specific port mappings
const redisPort = container.getMappedPort(6379);
const customPort = container.getMappedPort(6380);

Startup Timeout:

import { Duration } from "testcontainers";

const container = await new RedisContainer("redis:7")
  .withStartupTimeout(Duration.ofSeconds(300)) // 5 minutes for slow environments
  .start();

Container Reuse:

// Enable container reuse (requires testcontainers configuration)
const container = await new RedisContainer("redis:7")
  .withReuse(true)
  .start();

File System Operations:

// Copy files into container
const container = await new RedisContainer("redis:7")
  .withCopyFilesToContainer([
    {
      source: "./redis.conf",
      target: "/usr/local/etc/redis/redis.conf",
    },
  ])
  .start();

// Bind mount host directory
const container = await new RedisContainer("redis:7")
  .withBindMounts([
    {
      source: "./data",
      target: "/data",
    },
  ])
  .start();

Network Configuration:

// Use host network mode
const container = await new RedisContainer("redis:7")
  .withNetworkMode("host")
  .start();

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

const network = await Network.newNetwork().start();
const container = await new RedisContainer("redis:7")
  .withNetwork(network)
  .withNetworkAliases("redis")
  .start();

Resource Limits:

const container = await new RedisContainer("redis:7")
  .withMemory(2 * 1024 * 1024 * 1024) // 2GB
  .withCpuCount(2)
  .start();

Lifecycle Management

Proper Cleanup Pattern:

import { RedisContainer } from "@testcontainers/redis";

let container: StartedRedisContainer | null = null;

try {
  container = await new RedisContainer("redis:7").start();
  // Use container...
} finally {
  if (container) {
    await container.stop();
  }
}

With Test Framework Integration (Jest):

import { RedisContainer } from "@testcontainers/redis";
import { createClient } from "redis";

describe("Redis Tests", () => {
  let container: StartedRedisContainer;

  beforeAll(async () => {
    container = await new RedisContainer("redis:7").start();
  });

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

  it("should execute operations", async () => {
    const client = createClient({ url: container.getConnectionUrl() });
    await client.connect();
    // Test code...
    client.destroy();
  });
});

With Test Framework Integration (Mocha):

import { RedisContainer } from "@testcontainers/redis";
import { createClient } from "redis";

describe("Redis Tests", function () {
  let container: StartedRedisContainer;

  before(async function () {
    container = await new RedisContainer("redis:7").start();
  });

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

  it("should execute operations", async function () {
    const client = createClient({ url: container.getConnectionUrl() });
    await client.connect();
    // Test code...
    client.destroy();
  });
});

Container Restart:

const container = await new RedisContainer("redis:7")
  .withPersistence(dataDir)
  .start();

// Use container...
await container.restart(); // Restart the container
// Container is ready again after restart
// Data persists if withPersistence() was used

Error Handling

Startup Failures:

import { RedisContainer } from "@testcontainers/redis";

try {
  const container = await new RedisContainer("redis:7").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 "redis";

const container = await new RedisContainer("redis:7").start();

try {
  const client = createClient({ url: container.getConnectionUrl() });
  
  // Wait for container to be fully ready
  await new Promise((resolve) => setTimeout(resolve, 1000));
  
  await client.connect();
  await client.set("key", "value");
} 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 if (error.message.includes("NOAUTH")) {
    console.error("Authentication required - password missing or incorrect");
  } else {
    console.error("Connection error:", error);
  }
  throw error;
}

Redis Command Errors:

try {
  await client.set("key", "value");
  const value = await client.get("key");
} catch (error) {
  if (error.message.includes("WRONGTYPE")) {
    console.error("Wrong data type operation");
  } else if (error.message.includes("NOAUTH")) {
    console.error("Authentication required");
  } else {
    console.error("Redis error:", error.message);
  }
}

CLI Command Execution Errors:

try {
  const output = await container.executeCliCmd("info", ["clients"]);
  console.log(output);
} catch (error) {
  console.error("CLI command failed:", error.message);
  // Check command syntax, container state
}

Multiple Containers

Parallel Test Execution:

import { RedisContainer } from "@testcontainers/redis";

// Each test gets its own container
const test1 = async () => {
  const container = await new RedisContainer("redis:7").start();
  // Test code...
  await container.stop();
};

const test2 = async () => {
  const container = await new RedisContainer("redis:7").start();
  // Test code...
  await container.stop();
};

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

Multi-Container Scenarios:

import { RedisContainer } from "@testcontainers/redis";
import { Network } from "testcontainers";

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

try {
  // Create multiple containers on same network
  const container1 = await new RedisContainer("redis:7")
    .withNetwork(network)
    .withNetworkAliases("redis1")
    .start();

  const container2 = await new RedisContainer("redis:7")
    .withNetwork(network)
    .withNetworkAliases("redis2")
    .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();
}

Redis Stack for Extended Functionality

Redis Stack provides additional modules like JSON, Search, and Time Series:

import { RedisContainer } from "@testcontainers/redis";
import { createClient } from "redis";

await using container = await new RedisContainer("redis/redis-stack:latest")
  .withPassword("testPassword")
  .start();

const client = createClient({ url: container.getConnectionUrl() });
await client.connect();

// Use JSON module
await client.json.set("user", "$", { name: "Alice", age: 30 });
const user = await client.json.get("user");
console.log(user); // { name: "Alice", age: 30 }

// Use Search module
await client.ft.create("idx", {
  "$.name": { type: "TEXT", AS: "name" },
});

// Use Time Series module
await client.ts.create("temperature", {
  RETENTION: 3600000,
});

client.destroy();

Data Persistence Across Restarts

Enable persistence to maintain data across container restarts:

import { RedisContainer } from "@testcontainers/redis";
import { createClient } from "redis";
import fs from "fs";

const dataDir = fs.mkdtempSync("redis-");

await using container = await new RedisContainer("redis:7")
  .withPassword("test")
  .withPersistence(dataDir)
  .start();

let client = createClient({ url: container.getConnectionUrl() });
await client.connect();
await client.set("key", "value");
client.destroy();

// Restart container
await container.restart();
client = createClient({ url: container.getConnectionUrl() });
await client.connect();

// Data persisted
const value = await client.get("key"); // "value"

client.destroy();
fs.rmSync(dataDir, { force: true, recursive: true });

Loading Initial Data

Preload data using Redis command script files:

import { RedisContainer } from "@testcontainers/redis";
import { createClient } from "redis";

// Create seed-data.redis file with Redis commands:
// SET user:001 "John Doe"
// SET user:002 "Jane Smith"
// HSET user:003 name "Bob" age "25"

await using container = await new RedisContainer("redis:7")
  .withPassword("test")
  .withInitialData("./seed-data.redis")
  .start();

const client = createClient({ url: container.getConnectionUrl() });
await client.connect();

const user = await client.get("user:001"); // "John Doe"
const userInfo = await client.hGetAll("user:003"); // { name: "Bob", age: "25" }

client.destroy();

Custom Redis Configuration

Leverage inherited methods from GenericContainer for advanced configuration:

import { RedisContainer } from "@testcontainers/redis";

await using container = await new RedisContainer("redis:7")
  .withCommand(["redis-server", "--loglevel", "verbose", "--maxmemory", "256mb"])
  .withEnvironment({ CUSTOM_VAR: "value" })
  .withStartupTimeout(Duration.ofSeconds(120))
  .start();

// Custom configuration applied

Health Checks and Readiness

Custom Health Check:

import { RedisContainer } from "@testcontainers/redis";
import { Wait } from "testcontainers";

const container = await new RedisContainer("redis:7")
  .withWaitStrategy(
    Wait.forLogMessage("Ready to accept connections")
      .withStartupTimeout(Duration.ofSeconds(300))
  )
  .start();

Verifying Container Readiness:

const container = await new RedisContainer("redis:7").start();

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

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

Performance Considerations

Connection Pooling:

import { createClient } from "redis";

const container = await new RedisContainer("redis:7").start();

// Create client with connection pooling
const client = createClient({
  url: container.getConnectionUrl(),
  socket: {
    reconnectStrategy: (retries) => Math.min(retries * 50, 1000),
  },
});

await client.connect();

// Reuse client across multiple operations
for (let i = 0; i < 100; i++) {
  await client.set(`key:${i}`, `value:${i}`);
}

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

Batch Operations:

const client = createClient({ url: container.getConnectionUrl() });
await client.connect();

// Batch operations using pipeline
const pipeline = client.multi();
for (let i = 0; i < 1000; i++) {
  pipeline.set(`key:${i}`, `value:${i}`);
}
await pipeline.exec();

// Or use batch set
await client.mSet(
  Object.fromEntries(
    Array.from({ length: 1000 }, (_, i) => [`key:${i}`, `value:${i}`])
  )
);

Types

import { Readable } from "stream";

interface RestartOptions {
  timeout?: number;
}

interface StopOptions {
  timeout?: number;
  remove?: boolean;
}

interface StoppedTestContainer {
  getId(): string;
}

interface ExecOptions {
  user?: string;
  workingDir?: string;
  env?: Record<string, string>;
}

interface ExecResult {
  exitCode: number;
  output: string;
  stdout: string;
  stderr: string;
}

interface Labels {
  [key: string]: string;
}

interface FileToCopy {
  source: string;
  target: string;
  mode?: number;
}

interface DirectoryToCopy {
  source: string;
  target: string;
  mode?: number;
}

interface ContentToCopy {
  content: string | Buffer | Readable;
  target: string;
  mode?: number;
}

interface CommitOptions {
  /**
   * Repository name and optionally tag for the image (e.g., "my-image:tag")
   */
  repository?: string;
  /**
   * Tag for the image (alternative to including in repository)
   */
  tag?: string;
  /**
   * Commit message
   */
  comment?: string;
  /**
   * Author of the commit (e.g., "John Doe <john@example.com>")
   */
  author?: string;
  /**
   * Whether container should be paused during commit
   */
  pause?: boolean;
  /**
   * Additional Dockerfile instructions to apply (e.g., ["ENV TEST=true"])
   */
  changes?: string[];
  /**
   * If true, the image will be cleaned up by reaper on exit
   */
  deleteOnExit?: boolean;
}

Note: Readable is from Node.js built-in stream module.

Integration with Redis Clients

The module is designed to work seamlessly with standard Redis client libraries. The getConnectionUrl() method provides a properly formatted connection string:

import { RedisContainer } from "@testcontainers/redis";
import { createClient } from "redis"; // npm package: redis

await using container = await new RedisContainer("redis:7")
  .withPassword("mypassword")
  .start();

// Method 1: Using getConnectionUrl() (recommended)
const client = createClient({ url: container.getConnectionUrl() });
await client.connect();

// Method 2: Using individual getters
const client2 = createClient({
  socket: {
    host: container.getHost(),
    port: container.getPort(),
  },
  password: container.getPassword(),
});
await client2.connect();

Default Configuration

The module applies the following default configuration:

  • Default Port: 6379 (Redis standard port)
  • Default Password: None (empty string)
  • Default Persistence: Disabled (data lost on restart)
  • Default Initial Data: None (empty container)
  • Health Check: Redis PING command
  • Startup Timeout: Inherited from GenericContainer (typically 60 seconds)
  • Port Mapping: Random available ports on host

These defaults can be overridden using the withPassword(), withPersistence(), and withInitialData() 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.getConnectionUrl()
  • 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)

Authentication Errors:

  • Verify password is set if required: container.getPassword()
  • Ensure connection URL includes password (automatic with getConnectionUrl())
  • Check Redis client configuration matches container password

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 redis:7

Persistence Issues:

  • Ensure directory exists before container start
  • Check directory permissions (Docker must be able to mount)
  • Verify directory is not in use by another container
  • Use absolute paths for persistence directories

Initial Data Loading Failures:

  • Verify file exists and is readable
  • Check file format (must be valid Redis command script)
  • Ensure file path is correct (relative to working directory or absolute)
  • Check container logs for loading errors