Skip to content

Containers

Starting a container

Create and start any container using a Generic Container:

const { GenericContainer } = require("testcontainers");

const container = await new GenericContainer("alpine").start();

To use a specific image version:

const container = await new GenericContainer("alpine:3.10").start();

With a pull policy

Testcontainers will automatically pull an image if it doesn't exist. This is configurable:

const { GenericContainer, AlwaysPullPolicy } = require("testcontainers");

const container = await new GenericContainer("alpine")
  .withPullPolicy(new AlwaysPullPolicy())
  .start();

With a command

const container = await new GenericContainer("alpine")
  .withCommand(["sleep", "infinity"])
  .start();

With an entrypoint

const container = await new GenericContainer("alpine")
  .withEntrypoint(["cat"])
  .start();

With environment variables

const container = await new GenericContainer("alpine")
  .withEnvironment({ ENV: "VALUE" })
  .start();

With bind mounts

Not recommended.

Bind mounts are not portable. They do not work with Docker in Docker or in cases where the Docker agent is remote. It is preferred to copy files/content into the container instead.

const container = await new GenericContainer("alpine")
  .withBindMounts([{ 
    source: "/local/file.txt", 
    target:"/remote/file.txt" 
  }, {
    source: "/local/dir",
    target:"/remote/dir",
    mode: "ro"
  }])
  .start();

With labels

const container = await new GenericContainer("alpine")
  .withLabels({ label: "value" })
  .start();

With a name

Not recommended.

If a container with the same name already exists, Docker will raise a conflict. If you are specifying a name to enable container to container communication, look into creating a network and using network aliases.

const container = await new GenericContainer("alpine")
  .withName("custom-container-name")
  .start();

With files/content

const container = await new GenericContainer("alpine")
  .withCopyFilesToContainer([{ 
    source: "/local/file.txt", 
    target: "/remote/file1.txt"
  }])
  .withCopyContentToContainer([{ 
    content: "hello world",
    target: "/remote/file2.txt"
  }])
  .start();

With working directory

const container = await new GenericContainer("alpine")
  .withWorkingDir("/opt")
  .start();

With default log driver

May be necessary when the driver of your docker host does not support reading logs, and you want to use the log output wait strategy.

See log drivers.

const container = await new GenericContainer("alpine")
  .withDefaultLogDriver()
  .start();

With a tmpfs mount

const container = await new GenericContainer("alpine")
  .withTmpFs({ "/temp_pgdata": "rw,noexec,nosuid,size=65536k" })
  .start();

With user

Value can be a username or UID (format: <name|uid>[:<group|gid>]).

const container = await new GenericContainer("alpine")
  .withUser("bob")
  .start();

With privileged mode

const container = await new GenericContainer("alpine")
  .withPrivilegedMode()
  .start();

With added capabilities

See capabilities.

const container = await new GenericContainer("alpine")
  .withAddedCapabilities("NET_ADMIN", "IPC_LOCK")
  .start();

With dropped capabilities

See capabilities.

const container = await new GenericContainer("alpine")
  .withDroppedCapabilities("NET_ADMIN", "IPC_LOCK")
  .start();

With ulimits

Not supported in rootless container runtimes.

const container = await new GenericContainer("aline")
  .withUlimits({ 
    memlock: { 
      hard: -1, 
      soft: -1 
    }
  })
  .start();

With IPC mode

See IPC mode.

const container = await new GenericContainer("alpine")
  .withIpcMode("host")
  .start();

With resources quota

Not supported in rootless container runtimes.

See NanoCpu and Memory in ContainerCreate method.

  • Memory – Limit in Gigabytes
  • CPU – Quota in units of CPUs
const container = await new GenericContainer("alpine")
  .withResourcesQuota({ memory: 0.5, cpu: 1 })
  .start();

Stopping a container

Testcontainers by default will not wait until the container has stopped. It will simply issue the stop command and return immediately. This is to save time when running tests.

const container = await new GenericContainer("postgres").start();
await container.stop();

If you need to wait for the container to be stopped, you can provide a timeout:

const container = await new GenericContainer("postgres").start();
await container.stop({ timeout: 10000 }); // ms

Volumes created by the container are removed when stopped. This is configurable:

const container = await new GenericContainer("postgres").start();
await container.stop({ removeVolumes: false });

Restarting a container

const container = await new GenericContainer("alpine").start();
await container.restart();

Reusing a container

Enabling container re-use means that Testcontainers will not start a new container if a Testcontainers managed container with the same configuration is already running.

This is useful for example if you want to share a container across tests without global set up.

const container1 = await new GenericContainer("alpine")
  .withCommand(["sleep", "infinity"])
  .withReuse()
  .start();

const container2 = await new GenericContainer("alpine")
  .withCommand(["sleep", "infinity"])
  .withReuse()
  .start();

expect(container1.getId()).toBe(container2.getId());

Creating a custom container

You can create your own Generic Container as follows:

import {
  GenericContainer,
  TestContainer,
  StartedTestContainer,
  AbstractStartedContainer
} from "testcontainers";

class CustomContainer extends GenericContainer {
  constructor() {
    super("alpine");
  }

  public withCustomMethod(): this {
    // ...
    return this;
  }

  public override async start(): Promise<StartedCustomContainer> {
    return new StartedCustomContainer(await super.start());
  }
}

class StartedCustomContainer extends AbstractStartedContainer {
  constructor(startedTestContainer: StartedTestContainer) {
    super(startedTestContainer);
  }

  public withCustomMethod(): void {
    // ...
  }
}

const customContainer: TestContainer = new CustomContainer();
const startedCustomContainer: StartedTestContainer = await customContainer.start();

Lifecycle callbacks

Define your own lifecycle callbacks for better control over your custom containers:

import { 
  GenericContainer, 
  AbstractStartedContainer, 
  StartedTestContainer, 
  InspectResult 
} from "testcontainers";

class CustomContainer extends GenericContainer {
  protected override async beforeContainerStarted(): Promise<void> {
    // ...
  }

  protected override async containerCreated(containerId: string): Promise<void> {
    // ...
  }

  protected override async containerStarting(
    inspectResult: InspectResult,
    reused: boolean
  ): Promise<void> {
    // ...
  }

  protected override async containerStarted(
    container: StartedTestContainer,
    inspectResult: InspectResult,
    reused: boolean
  ): Promise<void> {
    // ...
  }

  public override async start(): Promise<CustomStartedContainer> {
    return new CustomStartedContainer(await super.start());
  }
}

class CustomStartedContainer extends AbstractStartedContainer {
  protected override async containerStopping(): Promise<void> {
    // ...
  }

  protected override async containerStopped(): Promise<void> {
    // ...
  }
}

Exposing container ports

Specify which container ports you want accessible by the host:

const container = await new GenericContainer("alpine")
  .withExposedPorts(22, 80, 443)
  .start();

Testcontainers will automatically bind an available, random port on the host to each exposed container port. This is to avoid port conflicts when running tests quickly or in parallel.

Retrieve the mapped port as follows:

const container = await new GenericContainer("alpine")
  .withExposedPorts(80)
  .start();

const httpPort = container.getMappedPort(80);

If a container exposes a single port, you can use the following convenience method:

const container = await new GenericContainer("alpine")
  .withExposedPorts(80)
  .start();

const httpPort = container.getFirstMappedPort();

Specify fixed host port bindings (not recommended):

const container = await new GenericContainer("alpine")
  .withExposedPorts({
    container: 80,
    host: 80
  })
  .start();

Running commands

const container = await new GenericContainer("alpine")
  .withCommand(["sleep", "infinity"])
  .start();

const { output, exitCode } = await container.exec(["echo", "hello", "world"]);

Streaming logs

const container = await new GenericContainer("alpine").start();

(await container.logs())
  .on("data", line => console.log(line))
  .on("err", line => console.error(line))
  .on("end", () => console.log("Stream closed"));