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).
Required Dependencies:
@testcontainers/redis (this package)@testcontainers/core is required (provided transitively)testcontainers package is required (provided transitively)redis npm package) is recommended for connecting to the container (not required by testcontainers itself)Default Behaviors:
6379 (Redis standard port, automatically exposed and mapped to random host port)redis:7 or redis/redis-stack:latest)getPassword())withPersistence() used)getPort() to retrieve)redis://:password@host:port (password omitted if empty)Threading Model:
start() method is async and must be awaitedstop() and restart() methods are async and must be awaitedexecuteCliCmd() is async and must be awaitedLifecycle:
await container.start() before usestart() resolvesawait container.stop() for cleanupstop() even on errorsawait 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 startExceptions:
ContainerStartException - Container failed to start (timeout, image pull failure, etc.)PortBindingException - Port binding conflictsDockerNotAvailableException - Docker daemon not accessibleImagePullException - Failed to pull Docker imageTimeoutError - Health check timeout (container started but not ready)Error from executeCliCmd() - Redis CLI command execution failures (non-zero exit code)RedisError from Redis clients - Redis protocol errors, command errorsConnectionError from Redis clients - Connection refused, timeout errorsEdge Cases:
withStartupTimeout())withReuse(true) to reuse containers across test runs (requires testcontainers configuration)withNetwork() and withNetworkAliases() for multi-container testsgetConnectionUrl() includes password automaticallyfs.mkdirSync() or fs.mkdtempSync() to createredis/redis-stack imageswithPersistence() was used; otherwise data is lostexecuteCliCmd() output: Returns stdout as string; stderr included if command failsgetPassword() returns empty string; getConnectionUrl() omits password sectionstart() to resolve before connecting clients; container may need additional time to be fully readynpm install @testcontainers/redis --save-devFor production dependencies (if needed):
npm install @testcontainers/redisimport { 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.
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();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();
}
}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' blockimport { 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");The module provides two main classes:
GenericContainer to provide Redis-specific configuration methods.The module automatically:
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();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' blockThe 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();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 usedStartup 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
}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 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();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 });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();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 appliedCustom 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);
}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}`])
)
);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.
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();The module applies the following default configuration:
These defaults can be overridden using the withPassword(), withPersistence(), and withInitialData() methods, or by using inherited Testcontainers methods for more advanced configuration.
Container Won't Start:
docker psawait container.logs()Connection Refused:
await container.start() completed successfullycontainer.getConnectionUrl()Health Check Timeout:
.withStartupTimeout(Duration.ofSeconds(300))Authentication Errors:
container.getPassword()getConnectionUrl())Port Conflicts in CI/CD:
Resource Exhaustion:
docker ps -adocker container pruneImage Pull Failures:
docker pull redis:7Persistence Issues:
Initial Data Loading Failures: