Source profileQuality 94/100

PramodDutta/qaskills/seed-skills/testcontainers-reuse-node/SKILL.md

Testcontainers Reuse (Node)

Teaches the agent to speed up Node integration tests with Testcontainers reuse — withReuse(true), TESTCONTAINERS_REUSE_ENABLE, the .testcontainers.properties opt-in, stable hashing for Postgres/MySQL/Kafka, and Ryuk/CI caveats.

Source repository stars
195
Declared platforms
4
Static risk flags
1
Last source update
2026-08-04
Source checked
2026-08-04

Decision brief

What it does—and where it fits

This skill makes the agent use Testcontainers' reuse feature to cut local integration-test startup from seconds to milliseconds, while avoiding the footguns: reuse is an explicit developer opt-in (it is off in CI by design), it requires a stable container configuration to hash,…

Best for

    Not for

    • Calling withReuse() but expecting CI to reuse. CI usually lacks the opt-in (and should) — reuse silently disables, masking the intent. Gate it on environment instead.
    • Calling container.stop() in teardown for a reusable container — it kills the very container the next run wanted to reuse.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexDeclaredSource recordInstall path and trigger
    Claude CodeDeclaredSource recordInstall path and trigger
    CursorDeclaredSource recordInstall path and trigger
    Gemini CLIDeclaredSource recordInstall path and trigger
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/testcontainers-reuse-node"
    Safe inspection promptEditorial

    Inspect the Agent Skill "Testcontainers Reuse (Node)" from https://github.com/PramodDutta/qaskills/blob/c924c5f7fee5fa410f267031061e492eb051757a/seed-skills/testcontainers-reuse-node/SKILL.md at commit c924c5f7fee5fa410f267031061e492eb051757a. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Workflow / Patterns

      Reuse needs a machine-level opt-in. The agent should instruct the user to create this file (it is intentionally not committed):

      Reuse needs a machine-level opt-in. The agent should instruct the user to create this file (it is intentionally not committed):
    2. 02

      Pattern 1 — Enable reuse globally (one-time developer setup)

      Reuse needs a machine-level opt-in. The agent should instruct the user to create this file (it is intentionally not committed):

      Reuse needs a machine-level opt-in. The agent should instruct the user to create this file (it is intentionally not committed):
    3. 03

      Core Principles

      1. Reuse is opt-in and local-only. It activates only when withReuse(true) is set AND testcontainers.reuse.enable=true is in the user's /.testcontainers.properties (or TESTCONTAINERSREUSEENABLE=true). It should stay off in CI, where clean state matters more than speed. 2. Reuse k…

      Reuse is opt-in and local-only. It activates only when withReuse(true) is set AND testcontainers.reuse.enable=true is in the user's /.testcontainers.properties (or TESTCONTAINERSREUSEENABLE=true). It should stay off in…Reuse keys on a config hash. Testcontainers hashes the container's configuration (image, ports, env, command, labels). Any change to that config produces a new container. Keep config stable to actually reuse.A reused container survives the test run. Ryuk (the resource-reaper) does not kill containers marked for reuse, so they linger for the next run. That is the point — but it means you must reset state between runs.
    4. 04

      /.testcontainers.properties

      testcontainers.reuse.enable=true bash export TESTCONTAINERSREUSEENABLE=true typescript import { PostgreSqlContainer, type StartedPostgreSqlContainer } from '@testcontainers/postgresql'; import { Client } from 'pg';

      Document the one-time opt-in (/.testcontainers.properties with testcontainers.reuse.enable=true) in the project README — without it, withReuse() is a no-op.Reset state in beforeEach (TRUNCATE / recreate topics), never by stopping the container. A reused container persists data by design.Do not call .stop() in afterAll for reusable containers; closing only the client connection is enough.
    5. 05

      Pattern 2 — A reusable Postgres container with stable hashing

      withReuse(true) plus a fixed name/labels. The data reset (TRUNCATE) is what makes reuse safe across runs.

      withReuse(true) plus a fixed name/labels. The data reset (TRUNCATE) is what makes reuse safe across runs.

    Permission review

    Static risk signals and limitations

    Writes files

    medium · line 21

    The documentation asks the agent to create, modify, or delete local files.

    Reuse needs a machine-level opt-in. The agent should instruct the user to create this file (it is intentionally not committed):

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score94/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars195SourceRepository attention, not individual Skill quality
    Compatibility4 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    PramodDutta/qaskills
    Skill path
    seed-skills/testcontainers-reuse-node/SKILL.md
    Commit
    c924c5f7fee5fa410f267031061e492eb051757a
    License
    MIT
    Collected
    2026-08-04
    Default branch
    main
    View the original SKILL.md

    Testcontainers Reuse (Node)

    This skill makes the agent use Testcontainers' reuse feature to cut local integration-test startup from seconds to milliseconds, while avoiding the footguns: reuse is an explicit developer opt-in (it is off in CI by design), it requires a stable container configuration to hash, and a reused container is not cleaned up by Ryuk, so test data must be reset by the test, not the container lifecycle.

    Use this skill when local integration tests are slow because every run boots a fresh Postgres/MySQL/Kafka, or when the user asks about withReuse, TESTCONTAINERS_REUSE_ENABLE, or "keep the container alive between runs."

    Core Principles

    1. Reuse is opt-in and local-only. It activates only when withReuse(true) is set AND testcontainers.reuse.enable=true is in the user's ~/.testcontainers.properties (or TESTCONTAINERS_REUSE_ENABLE=true). It should stay off in CI, where clean state matters more than speed.
    2. Reuse keys on a config hash. Testcontainers hashes the container's configuration (image, ports, env, command, labels). Any change to that config produces a new container. Keep config stable to actually reuse.
    3. A reused container survives the test run. Ryuk (the resource-reaper) does not kill containers marked for reuse, so they linger for the next run. That is the point — but it means you must reset state between runs.
    4. Reset data, not the container. Truncate tables, delete topics' messages, or use transactions/savepoints — do not rely on a fresh container per test.
    5. Reuse and .stop() are mutually exclusive in intent. Do not call .stop() in teardown for a reused container, or you defeat reuse on the next run.
    6. Pin images by digest/tag so the hash is deterministic across machines and runs.

    Workflow / Patterns

    Pattern 1 — Enable reuse globally (one-time developer setup)

    Reuse needs a machine-level opt-in. The agent should instruct the user to create this file (it is intentionally not committed):

    # ~/.testcontainers.properties
    testcontainers.reuse.enable=true
    

    Equivalent for the current shell (useful in scripts, NOT in CI):

    export TESTCONTAINERS_REUSE_ENABLE=true
    

    Without this, withReuse(true) is silently ignored and a fresh container starts every time.

    Pattern 2 — A reusable Postgres container with stable hashing

    withReuse(true) plus a fixed name/labels. The data reset (TRUNCATE) is what makes reuse safe across runs.

    import { PostgreSqlContainer, type StartedPostgreSqlContainer } from '@testcontainers/postgresql';
    import { Client } from 'pg';
    
    let container: StartedPostgreSqlContainer;
    let client: Client;
    
    beforeAll(async () => {
      container = await new PostgreSqlContainer('postgres:16-alpine')
        .withDatabase('appdb')
        .withUsername('test')
        .withPassword('test')
        // A stable label set keeps the config hash constant -> the same
        // container is reused on the next run.
        .withLabels({ project: 'my-app', purpose: 'integration' })
        .withReuse() // <-- opt into reuse
        .start();
    
      client = new Client({ connectionString: container.getConnectionUri() });
      await client.connect();
    
      // Schema is created idempotently because the container may already exist.
      await client.query(`
        CREATE TABLE IF NOT EXISTS users (
          id SERIAL PRIMARY KEY,
          email TEXT UNIQUE NOT NULL
        );
      `);
    });
    
    beforeEach(async () => {
      // Reset DATA, not the container. This is the key to safe reuse.
      await client.query('TRUNCATE TABLE users RESTART IDENTITY CASCADE');
    });
    
    afterAll(async () => {
      await client.end();
      // IMPORTANT: do NOT call container.stop() — that kills the reusable container.
    });
    
    test('inserts and reads a user', async () => {
      await client.query(`INSERT INTO users (email) VALUES ('[email protected]')`);
      const { rows } = await client.query('SELECT email FROM users');
      expect(rows).toEqual([{ email: '[email protected]' }]);
    });
    

    Pattern 3 — Share one reused container across many test files

    Reuse shines when several test files would each spin up their own DB. A small singleton returns the same started container; the hash makes them converge on one Docker container.

    // test/support/postgres.ts
    import { PostgreSqlContainer, type StartedPostgreSqlContainer } from '@testcontainers/postgresql';
    
    let started: Promise<StartedPostgreSqlContainer> | undefined;
    
    export function getPostgres(): Promise<StartedPostgreSqlContainer> {
      if (!started) {
        started = new PostgreSqlContainer('postgres:16-alpine')
          .withDatabase('appdb')
          .withUsername('test')
          .withPassword('test')
          .withReuse()
          .start();
      }
      return started;
    }
    
    // any.integration.test.ts
    import { getPostgres } from './support/postgres';
    
    test('uses the shared reusable container', async () => {
      const pg = await getPostgres();
      expect(pg.getConnectionUri()).toContain('appdb');
    });
    

    Pattern 4 — Reusable MySQL

    Same shape, different module. Idempotent schema + per-test reset.

    import { MySqlContainer, type StartedMySqlContainer } from '@testcontainers/mysql';
    import mysql from 'mysql2/promise';
    
    let container: StartedMySqlContainer;
    
    beforeAll(async () => {
      container = await new MySqlContainer('mysql:8.4')
        .withDatabase('appdb')
        .withUsername('test')
        .withUserPassword('test')
        .withReuse()
        .start();
    
      const conn = await mysql.createConnection(container.getConnectionUri());
      await conn.query(`CREATE TABLE IF NOT EXISTS orders (id INT PRIMARY KEY AUTO_INCREMENT, sku VARCHAR(64))`);
      await conn.end();
    });
    
    beforeEach(async () => {
      const conn = await mysql.createConnection(container.getConnectionUri());
      await conn.query('TRUNCATE TABLE orders');
      await conn.end();
    });
    

    Pattern 5 — Reusable Kafka (clean up topics, not the broker)

    Kafka boot is expensive, so reuse pays off most here. Reset by deleting topics rather than restarting the broker.

    import { KafkaContainer, type StartedKafkaContainer } from '@testcontainers/kafka';
    import { Kafka } from 'kafkajs';
    
    let container: StartedKafkaContainer;
    let kafka: Kafka;
    
    beforeAll(async () => {
      container = await new KafkaContainer('confluentinc/cp-kafka:7.6.1')
        .withReuse()
        .start();
    
      kafka = new Kafka({ brokers: [`${container.getHost()}:${container.getMappedPort(9093)}`] });
    });
    
    beforeEach(async () => {
      // Reset state by recreating the topic, not the broker.
      const admin = kafka.admin();
      await admin.connect();
      const topics = await admin.listTopics();
      if (topics.includes('events')) {
        await admin.deleteTopics({ topics: ['events'] });
      }
      await admin.createTopics({ topics: [{ topic: 'events', numPartitions: 1 }] });
      await admin.disconnect();
    });
    
    test('produces and consumes an event', async () => {
      const producer = kafka.producer();
      await producer.connect();
      await producer.send({ topic: 'events', messages: [{ value: 'hello' }] });
      await producer.disconnect();
      // ...consume and assert...
    });
    

    Pattern 6 — Keep reuse OFF in CI

    In CI, isolation beats speed and reused containers can poison subsequent jobs. Gate the behavior on environment.

    import { PostgreSqlContainer } from '@testcontainers/postgresql';
    
    const isCI = process.env.CI === 'true';
    
    const builder = new PostgreSqlContainer('postgres:16-alpine')
      .withDatabase('appdb')
      .withUsername('test')
      .withPassword('test');
    
    // Only reuse locally. In CI, start fresh and let Ryuk reap it.
    const container = await (isCI ? builder : builder.withReuse()).start();
    

    Best Practices

    1. Document the one-time opt-in (~/.testcontainers.properties with testcontainers.reuse.enable=true) in the project README — without it, withReuse() is a no-op.
    2. Reset state in beforeEach (TRUNCATE / recreate topics), never by stopping the container. A reused container persists data by design.
    3. Do not call .stop() in afterAll for reusable containers; closing only the client connection is enough.
    4. Pin images to a specific tag or digest so the reuse hash is deterministic across developers' machines.
    5. Keep the container config stable (fixed env, ports, labels). Adding a dynamic value to the config changes the hash and spawns a new container every run.
    6. Disable reuse in CI (CI env gate) so jobs get clean, Ryuk-reaped containers and never inherit stale state.
    7. Create schema idempotently (CREATE TABLE IF NOT EXISTS) because the container may already exist from a previous run.

    Anti-Patterns

    1. Calling withReuse() but expecting CI to reuse. CI usually lacks the opt-in (and should) — reuse silently disables, masking the intent. Gate it on environment instead.
    2. Calling container.stop() in teardown for a reusable container — it kills the very container the next run wanted to reuse.
    3. Relying on a fresh container per test for isolation. A reused container keeps old rows/topics. Reset data explicitly.
    4. Putting a timestamp/UUID into the container config (label, env, name) — it changes the hash each run, so nothing is ever reused.
    5. Leaving reuse on in CI, letting a stale container leak data into a later job and produce false passes/failures.
    6. Floating image tags (postgres:latest) — the hash and the pulled image drift, breaking reproducible reuse.
    7. Assuming Ryuk will clean up the reused container. It will not; that container is intentionally exempt and lingers until manually pruned (docker rm).

    When to Trigger This Skill

    • "My Testcontainers integration tests are slow on every run"
    • "How do I use withReuse(true) / TESTCONTAINERS_REUSE_ENABLE?"
    • "Keep the Postgres / MySQL / Kafka container alive between test runs"
    • "Reuse isn't working — it starts a new container every time"
    • "Should I enable Testcontainers reuse in CI?"
    • "Container data leaks between test runs"
    • "Where does .testcontainers.properties go?"
    • "Why doesn't Ryuk clean up my reused container?"

    Alternatives

    Compare before choosing