or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

index.mddocs/

LocalStack Module for Testcontainers

LocalStack module for Testcontainers that enables developers to programmatically start and manage LocalStack containers for testing AWS services locally. It provides a type-safe API for creating LocalStack containers with automatic port exposure, configurable wait strategies, and automatic hostname resolution for different networking scenarios.

Key Information for Agents

Required Dependencies:

  • @testcontainers/localstack (this package)
  • testcontainers package is required (provided transitively as peer dependency)
  • AWS SDK v3 client libraries for services you want to test (e.g., @aws-sdk/client-s3, @aws-sdk/client-sqs, @aws-sdk/client-lambda)
  • Docker daemon running and accessible
  • Node.js v18 or higher
  • Sufficient Docker resources to run containers

Default Behaviors:

  • Default port: 4566 (LocalStack service port, automatically exposed and mapped to random host port)
  • Default image: Must be specified in constructor (no default, typically localstack/localstack:3.0 or similar)
  • Default wait strategy: HTTP endpoint check on port 4566
  • Default startup timeout: 120 seconds
  • Default network: Bridge network (containers can communicate via host)
  • Default hostname resolution: localhost (unless network aliases or LOCALSTACK_HOST configured)
  • Port mapping: Random available ports on host (use getPort() or getConnectionUri() to retrieve)
  • Container reuse: Not enabled by default (new container per test)
  • Lambda container tracking: Automatically configured with Testcontainers session ID for cleanup
  • Credentials: Use dummy credentials (accessKeyId: 'test', secretAccessKey: 'test') for LocalStack

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 (each gets unique ports)
  • AWS SDK client operations are independent of container lifecycle
  • 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
  • Lambda containers spawned by LocalStack are automatically tracked and cleaned up by Testcontainers Reaper

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)
  • AWS SDK errors - Service-specific errors from AWS SDK clients (e.g., S3ServiceException, SQSServiceException)
  • Network errors - Connection failures when connecting to LocalStack endpoint

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)
  • 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
  • Lambda container cleanup: Lambda containers are automatically tracked, but ensure Testcontainers Reaper is running
  • Service availability: Not all AWS services are available in LocalStack; check LocalStack documentation for supported services
  • Endpoint configuration: Always use forcePathStyle: true for S3 clients when connecting to LocalStack
  • Region: LocalStack accepts any region, but us-east-1 is commonly used for testing
  • Credentials: LocalStack accepts any credentials, but test/test is standard for testing

Package Information

  • Package Name: @testcontainers/localstack
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install @testcontainers/localstack --save-dev
  • Repository: https://github.com/testcontainers/testcontainers-node
  • Documentation: https://node.testcontainers.org/modules/localstack/

Requirements

  • Docker daemon running and accessible
  • Node.js v18 or higher
  • Sufficient Docker resources to run containers

Note on Dependencies: The @testcontainers/localstack package includes testcontainers as a peer dependency. If you're also using the testcontainers package directly in your project for advanced features like custom networks, ensure you're using compatible versions to avoid TypeScript type conflicts.

Core Imports

import { LocalstackContainer, StartedLocalStackContainer } from '@testcontainers/localstack';

For CommonJS:

const { LocalstackContainer, StartedLocalStackContainer } = require('@testcontainers/localstack');

Basic Usage

Minimal Example

import { LocalstackContainer } from '@testcontainers/localstack';
import { S3Client, CreateBucketCommand, HeadBucketCommand } from '@aws-sdk/client-s3';

// Start a LocalStack container
const container = await new LocalstackContainer('localstack/localstack:3.0').start();

// Create an AWS SDK client pointing to LocalStack
const client = new S3Client({
  endpoint: container.getConnectionUri(),
  forcePathStyle: true,
  region: 'us-east-1',
  credentials: {
    secretAccessKey: 'test',
    accessKeyId: 'test',
  },
});

// Use AWS services
const createBucketCommand = new CreateBucketCommand({ Bucket: 'testcontainers' });
await client.send(createBucketCommand);

const headBucketCommand = new HeadBucketCommand({ Bucket: 'testcontainers' });
const response = await client.send(headBucketCommand);
console.log(response.$metadata.httpStatusCode); // 200

// Stop the container when done
await container.stop();

With Error Handling

import { LocalstackContainer } from '@testcontainers/localstack';
import { S3Client, CreateBucketCommand } from '@aws-sdk/client-s3';

let container: StartedLocalStackContainer | null = null;
let client: S3Client | null = null;

try {
  container = await new LocalstackContainer('localstack/localstack:3.0').start();
  
  client = new S3Client({
    endpoint: container.getConnectionUri(),
    forcePathStyle: true,
    region: 'us-east-1',
    credentials: {
      secretAccessKey: 'test',
      accessKeyId: 'test',
    },
  });

  const createBucketCommand = new CreateBucketCommand({ Bucket: 'testcontainers' });
  await client.send(createBucketCommand);
} catch (error) {
  console.error('Test failed:', error);
  throw error;
} finally {
  if (client) {
    client.destroy();
  }
  if (container) {
    await container.stop();
  }
}

Architecture

The LocalStack module is built on the Testcontainers framework and provides:

  • LocalstackContainer: Main container builder class that extends GenericContainer from the testcontainers package
  • StartedLocalStackContainer: Represents a running container with convenience methods for connection management
  • Automatic Configuration: Handles port exposure (4566), wait strategies, and hostname resolution automatically
  • Lambda Support: Configures session tracking for Lambda containers to ensure proper cleanup by the Testcontainers Reaper
  • Network Integration: Supports custom Docker networks with automatic hostname resolution based on network aliases

Capabilities

Creating LocalStack Containers

Create and configure a LocalStack container instance.

class LocalstackContainer extends GenericContainer {
  /**
   * Creates a new LocalStack container with the specified Docker image
   * @param image - Docker image name (e.g., 'localstack/localstack:3.0')
   * @throws {DockerException} - If Docker daemon is not available
   */
  constructor(image: string);

  /**
   * Starts the container and returns a StartedLocalStackContainer instance
   * @returns Promise resolving to a started container
   * @throws {ContainerStartException} - If container fails to start
   * @throws {TimeoutError} - If health check times out
   * @throws {ImagePullException} - If image pull fails
   */
  start(): Promise<StartedLocalStackContainer>;
}

Usage Example:

import { LocalstackContainer } from '@testcontainers/localstack';

// Create and start a container
const container = await new LocalstackContainer('localstack/localstack:3.0').start();

The LocalstackContainer class extends GenericContainer from the testcontainers package, which provides additional configuration methods:

Configuration Methods (inherited from GenericContainer):

/**
 * Set environment variables on the container
 * @param environment - Object with key-value pairs or single key-value
 */
withEnvironment(environment: { [key: string]: string }): this;
withEnvironment(key: string, value: string): this;

/**
 * Configure the container to use a specific Docker network
 * @param network - Network instance from testcontainers
 */
withNetwork(network: Network): this;

/**
 * Set network aliases for the container
 * @param aliases - One or more network alias strings
 */
withNetworkAliases(...aliases: string[]): this;

/**
 * Add additional exposed ports
 * @param ports - Port numbers to expose
 */
withExposedPorts(...ports: number[]): this;

/**
 * Set a custom wait strategy
 * @param waitStrategy - WaitStrategy instance
 */
withWaitStrategy(waitStrategy: WaitStrategy): this;

/**
 * Set the startup timeout in milliseconds
 * @param timeout - Timeout duration
 */
withStartupTimeout(timeout: Duration): this;

/**
 * Set the container command
 * @param command - Command array
 */
withCommand(command: string[]): this;

/**
 * Copy files or directories to the container before it starts
 * @param filesToCopy - Array of file copy configurations
 */
withCopyFilesToContainer(filesToCopy: CopyFilesToContainer[]): this;

/**
 * Bind mount a volume
 * @param bindMounts - Array of bind mount configurations
 */
withBindMounts(bindMounts: BindMount[]): this;

/**
 * Set temporary filesystem mounts
 * @param tmpFs - Object mapping paths to mount options
 */
withTmpFs(tmpFs: { [key: string]: string }): this;

/**
 * Set the user to run the container as
 * @param user - User string (e.g., 'root', '1000:1000')
 */
withUser(user: string): this;

/**
 * Enable privileged mode
 */
withPrivilegedMode(): this;

/**
 * Enable container reuse across test runs
 * @param reuse - Boolean to enable/disable reuse
 */
withReuse(reuse: boolean): this;

Advanced Configuration Example:

import { LocalstackContainer } from '@testcontainers/localstack';
import { Network } from 'testcontainers';

// Create a custom network
const network = await new Network().start();

// Create container with custom configuration
const container = await new LocalstackContainer('localstack/localstack:3.0')
  .withNetwork(network)
  .withNetworkAliases('localstack')
  .withEnvironment({
    SERVICES: 's3,sqs,lambda',
    DEBUG: '1'
  })
  .start();

Managing Started Containers

Access connection information and manage running LocalStack containers.

class StartedLocalStackContainer extends AbstractStartedContainer {
  /**
   * Returns the mapped host port for the LocalStack service (4566)
   * @returns The host port number that maps to LocalStack's port 4566
   * @throws {Error} - If container is not running
   */
  getPort(): number;

  /**
   * Returns a connection URI for connecting to LocalStack
   * @returns A connection URI in the form of `http://host:port`
   * @throws {Error} - If container is not running
   */
  getConnectionUri(): string;
}

The StartedLocalStackContainer class extends AbstractStartedContainer from the testcontainers package, which provides additional container management methods:

Container Management Methods (inherited from AbstractStartedContainer):

/**
 * Get the container's host address
 * @returns Host address string
 */
getHost(): string;

/**
 * Get the container's hostname
 * @returns Hostname string
 */
getHostname(): string;

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

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

/**
 * Get the first mapped port
 * @returns Port number
 */
getFirstMappedPort(): number;

/**
 * Get the mapped port for a container port
 * @param port - Container port number
 * @param protocol - Protocol ('tcp' or 'udp')
 * @returns Mapped host port number
 */
getMappedPort(port: number, protocol?: string): number;
getMappedPort(portWithProtocol: `${number}/${"tcp" | "udp"}`): number;

/**
 * Get container labels
 * @returns Labels object
 */
getLabels(): Labels;

/**
 * Get the container's network names
 * @returns Array of network names
 */
getNetworkNames(): string[];

/**
 * Get the network ID for a specific network
 * @param networkName - Network name
 * @returns Network ID string
 */
getNetworkId(networkName: string): string;

/**
 * Get the container's IP address on a specific network
 * @param networkName - Network name
 * @returns IP address string
 */
getIpAddress(networkName: string): string;

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

/**
 * Restart the container
 * @param options - Optional restart configuration
 * @returns Promise that resolves when container is restarted
 */
restart(options?: Partial<RestartOptions>): Promise<void>;

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

/**
 * Get the container logs
 * @param opts - Log options (since, tail)
 * @returns Promise resolving to readable stream
 */
logs(opts?: { since?: number; tail?: number }): Promise<Readable>;

/**
 * Execute a command in the running container
 * @param command - Command string or array
 * @param opts - Execution options
 * @returns Promise resolving to execution result
 */
exec(command: string | string[], opts?: Partial<ExecOptions>): Promise<ExecResult>;

/**
 * Copy files to the container
 * @param filesToCopy - Array of file copy configurations
 */
copyFilesToContainer(filesToCopy: FileToCopy[]): Promise<void>;

/**
 * Copy directories to the container
 * @param directoriesToCopy - Array of directory copy configurations
 */
copyDirectoriesToContainer(directoriesToCopy: DirectoryToCopy[]): Promise<void>;

/**
 * Copy content to the container
 * @param contentsToCopy - Array of content copy configurations
 */
copyContentToContainer(contentsToCopy: ContentToCopy[]): Promise<void>;

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

/**
 * Copy a tar archive from the container
 * @param path - Source path in container
 * @returns Promise resolving to readable stream
 */
copyArchiveFromContainer(path: string): Promise<NodeJS.ReadableStream>;

Usage Example:

import { LocalstackContainer } from '@testcontainers/localstack';

const container = await new LocalstackContainer('localstack/localstack:3.0').start();

// Get connection details
const host = container.getHost();
const port = container.getPort();
const uri = container.getConnectionUri();

console.log(`LocalStack is running at ${uri}`);

// Execute a command in the container
const result = await container.exec(['printenv', 'LOCALSTACK_HOST']);
console.log(result.output);

// Get container logs
const logs = await container.logs();
logs.on('data', (line) => console.log(line.toString()));

// Stop the container
await container.stop();

Network Configuration

The LocalStack module automatically configures the LOCALSTACK_HOST environment variable based on your networking setup:

  1. Explicit Configuration: If you set LOCALSTACK_HOST explicitly via withEnvironment(), that value is used
  2. Network Aliases: If you configure network aliases via withNetworkAliases(), the last alias is used
  3. Default: If neither of the above, defaults to localhost

Network Example:

import { LocalstackContainer } from '@testcontainers/localstack';
import { Network, GenericContainer } from 'testcontainers';

// Create a network
const network = await new Network().start();

try {
  // Start LocalStack with network alias
  const localstack = await new LocalstackContainer('localstack/localstack:3.0')
    .withNetwork(network)
    .withNetworkAliases('localstack')
    .start();

  // Start another container on the same network
  const appContainer = await new GenericContainer('my-app:latest')
    .withNetwork(network)
    .withEnvironment({
      AWS_ENDPOINT: 'http://localstack:4566'
    })
    .start();

  // The app container can now reach LocalStack via the 'localstack' hostname
} finally {
  await localstack?.stop();
  await appContainer?.stop();
  await network.stop();
}

Lambda Container Support

The LocalStack module automatically configures LAMBDA_DOCKER_FLAGS to include the Testcontainers session ID label. This ensures that Lambda containers started by LocalStack are properly tracked and cleaned up by the Testcontainers Reaper when your tests finish.

Lambda Example:

import { LocalstackContainer } from '@testcontainers/localstack';
import { LambdaClient, CreateFunctionCommand, InvokeCommand } from '@aws-sdk/client-lambda';

const container = await new LocalstackContainer('localstack/localstack:3.0')
  .withEnvironment({
    SERVICES: 'lambda'
  })
  .start();

const lambdaClient = new LambdaClient({
  endpoint: container.getConnectionUri(),
  region: 'us-east-1',
  credentials: {
    secretAccessKey: 'test',
    accessKeyId: 'test',
  },
});

// Create and invoke Lambda functions
// Lambda containers will be automatically cleaned up
const createFunctionCommand = new CreateFunctionCommand({
  FunctionName: 'test-function',
  Runtime: 'nodejs18.x',
  Handler: 'index.handler',
  Role: 'arn:aws:iam::123456789012:role/lambda-role',
  Code: {
    ZipFile: Buffer.from('exports.handler = async () => ({ statusCode: 200, body: "Hello" });')
  }
});

await lambdaClient.send(createFunctionCommand);

await container.stop();

Custom Environment Variables

You can override or add environment variables to customize LocalStack's behavior:

import { LocalstackContainer } from '@testcontainers/localstack';

const container = await new LocalstackContainer('localstack/localstack:3.0')
  .withEnvironment({
    SERVICES: 's3,sqs,dynamodb',  // Only start specific services
    DEBUG: '1',                    // Enable debug mode
    LOCALSTACK_HOST: 'myhost',     // Override hostname resolution
    SQS_ENDPOINT_STRATEGY: 'path', // Configure SQS endpoint strategy
    LAMBDA_DOCKER_FLAGS: '-l mylabel=myvalue', // Add custom Docker flags for Lambda
    PERSISTENCE: '1',              // Enable persistence (requires volume mount)
    DATA_DIR: '/tmp/localstack/data', // Set data directory for persistence
  })
  .start();

Common Environment Variables:

  • SERVICES: Comma-separated list of AWS services to start (e.g., 's3,sqs,lambda')
  • DEBUG: Set to '1' to enable debug logging
  • LOCALSTACK_HOST: Override hostname resolution
  • LAMBDA_DOCKER_FLAGS: Custom Docker flags for Lambda containers (session ID is automatically appended)
  • PERSISTENCE: Set to '1' to enable data persistence (requires volume mount)
  • DATA_DIR: Directory for persistent data storage
  • SQS_ENDPOINT_STRATEGY: SQS endpoint strategy ('path' or 'domain')
  • LAMBDA_EXECUTOR: Lambda execution mode ('local' or 'docker')

Note: When you provide LAMBDA_DOCKER_FLAGS, the Testcontainers session ID label will be automatically appended to your custom flags.

AWS SDK Integration Examples

S3 Service

import { LocalstackContainer } from '@testcontainers/localstack';
import { S3Client, CreateBucketCommand, PutObjectCommand, GetObjectCommand, DeleteBucketCommand } from '@aws-sdk/client-s3';

const container = await new LocalstackContainer('localstack/localstack:3.0')
  .withEnvironment({ SERVICES: 's3' })
  .start();

const s3Client = new S3Client({
  endpoint: container.getConnectionUri(),
  forcePathStyle: true, // Required for LocalStack
  region: 'us-east-1',
  credentials: {
    secretAccessKey: 'test',
    accessKeyId: 'test',
  },
});

// Create bucket
await s3Client.send(new CreateBucketCommand({ Bucket: 'test-bucket' }));

// Put object
await s3Client.send(new PutObjectCommand({
  Bucket: 'test-bucket',
  Key: 'test-key',
  Body: 'test content'
}));

// Get object
const getResponse = await s3Client.send(new GetObjectCommand({
  Bucket: 'test-bucket',
  Key: 'test-key'
}));

// Cleanup
await s3Client.send(new DeleteBucketCommand({ Bucket: 'test-bucket' }));
await container.stop();

SQS Service

import { LocalstackContainer } from '@testcontainers/localstack';
import { SQSClient, CreateQueueCommand, SendMessageCommand, ReceiveMessageCommand, DeleteQueueCommand } from '@aws-sdk/client-sqs';

const container = await new LocalstackContainer('localstack/localstack:3.0')
  .withEnvironment({ SERVICES: 'sqs' })
  .start();

const sqsClient = new SQSClient({
  endpoint: container.getConnectionUri(),
  region: 'us-east-1',
  credentials: {
    secretAccessKey: 'test',
    accessKeyId: 'test',
  },
});

// Create queue
const createResponse = await sqsClient.send(new CreateQueueCommand({
  QueueName: 'test-queue'
}));
const queueUrl = createResponse.QueueUrl!;

// Send message
await sqsClient.send(new SendMessageCommand({
  QueueUrl: queueUrl,
  MessageBody: 'test message'
}));

// Receive message
const receiveResponse = await sqsClient.send(new ReceiveMessageCommand({
  QueueUrl: queueUrl
}));

// Cleanup
await sqsClient.send(new DeleteQueueCommand({ QueueUrl: queueUrl }));
await container.stop();

DynamoDB Service

import { LocalstackContainer } from '@testcontainers/localstack';
import { DynamoDBClient, CreateTableCommand, PutItemCommand, GetItemCommand, DeleteTableCommand } from '@aws-sdk/client-dynamodb';

const container = await new LocalstackContainer('localstack/localstack:3.0')
  .withEnvironment({ SERVICES: 'dynamodb' })
  .start();

const dynamoClient = new DynamoDBClient({
  endpoint: container.getConnectionUri(),
  region: 'us-east-1',
  credentials: {
    secretAccessKey: 'test',
    accessKeyId: 'test',
  },
});

// Create table
await dynamoClient.send(new CreateTableCommand({
  TableName: 'test-table',
  KeySchema: [
    { AttributeName: 'id', KeyType: 'HASH' }
  ],
  AttributeDefinitions: [
    { AttributeName: 'id', AttributeType: 'S' }
  ],
  BillingMode: 'PAY_PER_REQUEST'
}));

// Put item
await dynamoClient.send(new PutItemCommand({
  TableName: 'test-table',
  Item: {
    id: { S: '1' },
    name: { S: 'test' }
  }
}));

// Get item
const getResponse = await dynamoClient.send(new GetItemCommand({
  TableName: 'test-table',
  Key: { id: { S: '1' } }
}));

// Cleanup
await dynamoClient.send(new DeleteTableCommand({ TableName: 'test-table' }));
await container.stop();

Test Framework Integration

Jest/Vitest Setup

import { LocalstackContainer } from '@testcontainers/localstack';
import { S3Client, CreateBucketCommand } from '@aws-sdk/client-s3';

describe('AWS Service Tests', () => {
  let container: StartedLocalStackContainer;
  let s3Client: S3Client;

  beforeAll(async () => {
    container = await new LocalstackContainer('localstack/localstack:3.0').start();
    
    s3Client = new S3Client({
      endpoint: container.getConnectionUri(),
      forcePathStyle: true,
      region: 'us-east-1',
      credentials: {
        secretAccessKey: 'test',
        accessKeyId: 'test',
      },
    });
  });

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

  it('should create an S3 bucket', async () => {
    await s3Client.send(new CreateBucketCommand({ Bucket: 'test-bucket' }));
  });
});

Mocha Setup

import { LocalstackContainer } from '@testcontainers/localstack';
import { S3Client, CreateBucketCommand } from '@aws-sdk/client-s3';

describe('AWS Service Tests', function () {
  let container: StartedLocalStackContainer;
  let s3Client: S3Client;

  before(async function () {
    container = await new LocalstackContainer('localstack/localstack:3.0').start();
    
    s3Client = new S3Client({
      endpoint: container.getConnectionUri(),
      forcePathStyle: true,
      region: 'us-east-1',
      credentials: {
        secretAccessKey: 'test',
        accessKeyId: 'test',
      },
    });
  });

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

  it('should create an S3 bucket', async function () {
    await s3Client.send(new CreateBucketCommand({ Bucket: 'test-bucket' }));
  });
});

Per-Test Container Setup

import { LocalstackContainer } from '@testcontainers/localstack';
import { S3Client, CreateBucketCommand } from '@aws-sdk/client-s3';

describe('AWS Service Tests', () => {
  it('should create an S3 bucket', async () => {
    const container = await new LocalstackContainer('localstack/localstack:3.0').start();
    
    try {
      const s3Client = new S3Client({
        endpoint: container.getConnectionUri(),
        forcePathStyle: true,
        region: 'us-east-1',
        credentials: {
          secretAccessKey: 'test',
          accessKeyId: 'test',
        },
      });

      await s3Client.send(new CreateBucketCommand({ Bucket: 'test-bucket' }));
      s3Client.destroy();
    } finally {
      await container.stop();
    }
  });
});

Multiple Containers

Parallel Test Execution

import { LocalstackContainer } from '@testcontainers/localstack';
import { S3Client, CreateBucketCommand } from '@aws-sdk/client-s3';

// Each test gets its own container
const test1 = async () => {
  const container = await new LocalstackContainer('localstack/localstack:3.0').start();
  try {
    const s3Client = new S3Client({
      endpoint: container.getConnectionUri(),
      forcePathStyle: true,
      region: 'us-east-1',
      credentials: { secretAccessKey: 'test', accessKeyId: 'test' },
    });
    await s3Client.send(new CreateBucketCommand({ Bucket: 'bucket1' }));
    s3Client.destroy();
  } finally {
    await container.stop();
  }
};

const test2 = async () => {
  const container = await new LocalstackContainer('localstack/localstack:3.0').start();
  try {
    const s3Client = new S3Client({
      endpoint: container.getConnectionUri(),
      forcePathStyle: true,
      region: 'us-east-1',
      credentials: { secretAccessKey: 'test', accessKeyId: 'test' },
    });
    await s3Client.send(new CreateBucketCommand({ Bucket: 'bucket2' }));
    s3Client.destroy();
  } finally {
    await container.stop();
  }
};

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

Multi-Container Scenarios

import { LocalstackContainer } from '@testcontainers/localstack';
import { Network } from 'testcontainers';
import { S3Client, CreateBucketCommand } from '@aws-sdk/client-s3';

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

try {
  // Create multiple containers on same network
  const container1 = await new LocalstackContainer('localstack/localstack:3.0')
    .withNetwork(network)
    .withNetworkAliases('localstack1')
    .start();

  const container2 = await new LocalstackContainer('localstack/localstack:3.0')
    .withNetwork(network)
    .withNetworkAliases('localstack2')
    .start();

  // Each container has its own endpoint
  const s3Client1 = new S3Client({
    endpoint: container1.getConnectionUri(),
    forcePathStyle: true,
    region: 'us-east-1',
    credentials: { secretAccessKey: 'test', accessKeyId: 'test' },
  });

  const s3Client2 = new S3Client({
    endpoint: container2.getConnectionUri(),
    forcePathStyle: true,
    region: 'us-east-1',
    credentials: { secretAccessKey: 'test', accessKeyId: 'test' },
  });

  // Use both containers...
} finally {
  await container1?.stop();
  await container2?.stop();
  await network.stop();
}

Constants

The package exports the following constants:

/**
 * The default LocalStack service port
 */
const LOCALSTACK_PORT: 4566;

Types

The main types are exported from the package. Additional types are inherited from the testcontainers package:

import type {
  Network,
  WaitStrategy,
  BindMount,
  CopyFilesToContainer,
  FileToCopy,
  DirectoryToCopy,
  ContentToCopy,
  ExecResult,
  ExecOptions,
  StoppedTestContainer,
  StopOptions,
  RestartOptions,
  CommitOptions,
  Labels,
  Duration
} from 'testcontainers';

import type { Readable } from 'stream';

Error Handling

Container operations can throw errors. It's recommended to handle them appropriately:

import { LocalstackContainer } from '@testcontainers/localstack';
import { S3Client, CreateBucketCommand, S3ServiceException } from '@aws-sdk/client-s3';

let container: StartedLocalStackContainer | null = null;
let s3Client: S3Client | null = null;

try {
  container = await new LocalstackContainer('localstack/localstack:3.0').start();
  
  s3Client = new S3Client({
    endpoint: container.getConnectionUri(),
    forcePathStyle: true,
    region: 'us-east-1',
    credentials: {
      secretAccessKey: 'test',
      accessKeyId: 'test',
    },
  });

  await s3Client.send(new CreateBucketCommand({ Bucket: 'test-bucket' }));
} 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 if (error instanceof S3ServiceException) {
    console.error('S3 service error:', error.message);
    // Handle AWS service-specific errors
  } else {
    console.error('Unexpected error:', error);
  }
  throw error;
} finally {
  if (s3Client) {
    s3Client.destroy();
  }
  if (container) {
    await container.stop();
  }
}

Common Error Scenarios:

  • Docker daemon not running: Check Docker status and accessibility
  • Image pull failures: Verify network connectivity and image name/tag
  • Port conflicts: Testcontainers handles this automatically, but conflicts can occur in CI/CD
  • Startup timeout exceeded: Increase timeout with withStartupTimeout() or check system resources
  • Network configuration issues: Verify network setup and aliases
  • AWS SDK connection errors: Ensure container is fully started before creating clients
  • Service not available: Check that the service is included in SERVICES environment variable
  • Authentication errors: Verify credentials are set correctly (use test/test for LocalStack)

Advanced Scenarios

Custom Wait Strategy

import { LocalstackContainer } from '@testcontainers/localstack';
import { Wait, Duration } from 'testcontainers';

const container = await new LocalstackContainer('localstack/localstack:3.0')
  .withWaitStrategy(
    Wait.forHttp('/_localstack/health', 4566)
      .forStatusCode(200)
      .withStartupTimeout(Duration.ofSeconds(300))
  )
  .start();

Container Reuse

import { LocalstackContainer } from '@testcontainers/localstack';

// Enable container reuse (requires testcontainers configuration)
const container = await new LocalstackContainer('localstack/localstack:3.0')
  .withReuse(true)
  .start();

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

Data Persistence

import { LocalstackContainer } from '@testcontainers/localstack';

// Enable persistence with volume mount
const container = await new LocalstackContainer('localstack/localstack:3.0')
  .withEnvironment({
    PERSISTENCE: '1',
    DATA_DIR: '/tmp/localstack/data'
  })
  .withBindMounts([
    {
      source: './localstack-data',
      target: '/tmp/localstack/data'
    }
  ])
  .start();

Custom Startup Timeout

import { LocalstackContainer } from '@testcontainers/localstack';
import { Duration } from 'testcontainers';

// Increase startup timeout for slower systems
const container = await new LocalstackContainer('localstack/localstack:3.0')
  .withStartupTimeout(Duration.ofSeconds(300))
  .start();

Log Consumption

import { LocalstackContainer } from '@testcontainers/localstack';
import { OutputFrame } from 'testcontainers';

const container = await new LocalstackContainer('localstack/localstack:3.0')
  .withLogConsumer((frame: OutputFrame) => {
    console.log(frame.getUtf8String());
  })
  .start();

Troubleshooting

Container Won't Start:

  • Verify Docker is running: docker ps
  • Check Docker daemon accessibility
  • Verify image name and tag are correct (e.g., localstack/localstack:3.0)
  • Check for port conflicts (though testcontainers handles this automatically)
  • Review container logs: await container.logs()
  • Check system resources (memory, CPU, disk space)

Connection Refused:

  • Ensure await container.start() completed successfully
  • Wait a moment after start() resolves for container to be fully ready
  • Verify using container.getConnectionUri() and container.getPort()
  • Check container logs for errors
  • Verify the service is included in SERVICES environment variable

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)
  • Verify LocalStack version supports the requested services

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
  • Check Docker-in-Docker configuration if running in containers

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
  • Monitor Docker resource usage: docker stats

Image Pull Failures:

  • Check network connectivity
  • Verify image registry accessibility
  • Use specific image tags instead of "latest"
  • Pre-pull images: docker pull localstack/localstack:3.0
  • Check Docker registry authentication if using private images

AWS SDK Connection Errors:

  • Ensure forcePathStyle: true is set for S3 clients
  • Verify endpoint URL format: http://host:port
  • Check that credentials are set (use test/test for LocalStack)
  • Verify region is set (any region works, but us-east-1 is common)
  • Ensure container is fully started before creating clients

Service Not Available:

  • Check that service is included in SERVICES environment variable
  • Verify LocalStack version supports the service
  • Check LocalStack documentation for service availability
  • Review container logs for service startup errors

Lambda Container Cleanup Issues:

  • Ensure Testcontainers Reaper is running
  • Verify LAMBDA_DOCKER_FLAGS includes session tracking (automatic)
  • Check Docker for orphaned Lambda containers: docker ps -a | grep lambda
  • Manually clean up if needed: docker container prune