npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cf-container-warm-pool

v0.1.2

Published

A warm pool manager for Cloudflare Containers - pre-warm containers and acquire them on-demand

Readme

cf-container-warm-pool

A warm pool manager for Cloudflare Containers and the Sandbox SDK. Pre-warm containers and acquire them on-demand with automatic lifecycle management.

This library pre-warms containers to reduce cold start times for end users. You can use it to start containers ahead of time and run any necessary setup before requests arrive.

This is a temporary solution. Cloudflare plans to make this unnecessary with faster default start times using disk and memory snapshots. Use this library now, then remove it once that functionality ships.

Cost consideration: Pre-warmed containers are billed while sitting in the pool. This is the main tradeoff of using this library.

Installation

npm install cf-container-warm-pool @cloudflare/containers

Quick Start

import { Container } from '@cloudflare/containers';
import { createWarmPool, getWarmPool, WarmPool } from 'cf-container-warm-pool';

// Define your container with optional (but recommended) onStop handler
export class MyContainer extends Container<Env> {
  defaultPort = 8080;
  sleepAfter = '5m';

  // RECOMMENDED: Notify the pool immediately when this container stops
  async onStop() {
    const pool = getWarmPool(this.env.WARM_POOL);
    await pool.reportStopped(this.ctx.id.toString());
  }
}

// Export the WarmPool DO
export { WarmPool };

// Use in your Worker
export default {
  async fetch(request: Request, env: Env) {
    const pool = createWarmPool(env.WARM_POOL, env.CONTAINER, {
      warmTarget: 3,
    });

    // Get a container by ID (1:1 mapping, sticky sessions)
    const sessionId = request.headers.get('x-session-id') || 'default';
    const container = await pool.getContainer(sessionId);
    return container.fetch(request);
  }
};

Recommended: Container onStop Handler

Implement onStop() in your container class to notify the pool immediately when a container stops:

export class MyContainer extends Container<Env> {
  async onStop() {
    const pool = getWarmPool(this.env.WARM_POOL);
    await pool.reportStopped(this.ctx.id.toString());
  }
}

This is optional but preferred - it allows the pool to immediately remove stopped containers and replenish warm ones without waiting for the next health check.

As a fallback, the pool runs health checks each refreshInterval using the Container's built-in getState() method. This catches containers that stopped without reporting (e.g., if onStop() failed or wasn't implemented).

Configuration

Configure your wrangler.jsonc:

{
  "name": "my-app",
  "main": "src/index.ts",
  "compatibility_date": "2025-01-13",

  "containers": [
    {
      "class_name": "MyContainer",
      "image": "./Dockerfile",
      "max_instances": 10,
      "name": "my-container"
    }
  ],

  "durable_objects": {
    "bindings": [
      // Your existing container binding
      { "class_name": "MyContainer", "name": "CONTAINER" },
      // Add this new binding for the warm pool
      { "class_name": "WarmPool", "name": "WARM_POOL" }
    ]
  },

  "kv_namespaces": [
    // Shared cache for optional idCache
    {
      "binding": "CONTAINER_ID_CACHE",
      "id": "<kv-id>",
      "preview_id": "<kv-preview-id>"
    }
  ],

  "migrations": [
    // Your existing container migration - skip if you already have this
    { "tag": "v1", "new_sqlite_classes": ["MyContainer"] },
    // Add this new migration for the warm pool
    { "tag": "v2", "new_sqlite_classes": ["WarmPool"] }
  ]
}

API

createWarmPool(poolNamespace, containerNamespace, config?)

Creates a warm pool client.

Recommended: pass idCache for production to avoid an extra warm-pool DO hop on repeat requests.

Config options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | warmTarget | number | 5 | Target number of warm (unassigned) containers to maintain ready for immediate use | | refreshInterval | number | 10000 | How often to check health and replenish warm containers (ms) | | poolName | string | 'global-pool' | Name of the pool instance. Use this if you have multiple container types and need separate warm pools. | | idCache | KVNamespace | undefined | Optional KV namespace for cached sessionId -> containerId lookups. If provided, getContainer() checks KV first and falls back to the DO on cache miss or invalid mapping. |

Important: Your container's sleepAfter must be longer than refreshInterval. The pool renews the activity timeout on warm containers each refresh cycle to keep them alive. If sleepAfter is shorter than refreshInterval, containers may stop before the next refresh.

idCache (recommended)

You can pass idCache to use KV as an optional container-id lookup cache and bypass the warm-pool DO for fast path routing (recommended default for production):

  • idCache stores poolName + ':' + id -> containerId mappings.
  • getContainer(id) checks KV first.
  • On KV hit, the client validates that container state is healthy/running before use.
  • If the cached container is missing or not running, the cache key is deleted, it falls back to WARM_POOL.getContainer(id), then writes the resolved containerId back to KV.
  • This gives fast-path routing without routing requests to wrong/dead instances.

Example:

export interface Env {
  CONTAINER: DurableObjectNamespace;
  WARM_POOL: DurableObjectNamespace<WarmPool>;
  CONTAINER_ID_CACHE: KVNamespace;
}

const pool = createWarmPool(env.WARM_POOL, env.CONTAINER, {
  poolName: "global-pool",
  idCache: env.CONTAINER_ID_CACHE,
  warmTarget: 3,
});

wrangler.jsonc binding:

"kv_namespaces": [
  {
    "binding": "CONTAINER_ID_CACHE",
    "id": "<kv-id>",
    "preview_id": "<kv-preview-id>"
  }
]

getWarmPool(poolNamespace, poolName?)

Get the WarmPool Durable Object stub. Use this in your container's onStop() to call reportStopped().

const pool = getWarmPool(this.env.WARM_POOL);
await pool.reportStopped(this.ctx.id.toString());

If using multiple pools, pass the same poolName you used in createWarmPool:

const pool = getWarmPool(this.env.WARM_POOL, 'my-custom-pool');
await pool.reportStopped(this.ctx.id.toString());

pool.getContainer(id)

Get a container by ID.

  • If this ID already has an assigned container, returns the same container (1:1 mapping)
  • If not, assigns a warm container from the pool
  • If no warm containers available, starts a new one
const container = await pool.getContainer('user-session-123');
const response = await container.fetch(request);

pool.stats()

Get current pool statistics.

const stats = await pool.stats();
// { warm: 3, assigned: 2, total: 5, config: {...} }

pool.shutdownPrewarmed()

Stop all pre-warmed (unassigned) containers. Does not affect containers that are assigned to user IDs.

await pool.shutdownPrewarmed();

How It Works

  1. Pre-warming: The pool maintains warmTarget containers ready for immediate use

  2. 1:1 mapping: getContainer(id) always returns the same container for the same ID - no sharing between IDs

  3. Automatic assignment: If no container is assigned to an ID, a warm one is taken from the pool

  4. Container lifecycle: Containers manage their own lifecycle via sleepAfter. When they stop, onStop() notifies the pool

  5. Health checks: Each refresh interval, the pool checks all tracked containers via isRunning() (if implemented) and removes any that have stopped

  6. Pool refresh: A background alarm replenishes warm containers to maintain warmTarget

Example

See the /example directory for a complete working example including:

  • Container Dockerfile
  • Worker code with routing
  • wrangler.jsonc configuration
cd example
npm install
npm run dev

Sandbox SDK Example

This library also works with the Sandbox SDK (@cloudflare/sandbox). The Sandbox extends the Container class with methods for code execution, file operations, and more.

import { Sandbox } from '@cloudflare/sandbox';
import { createWarmPool, getWarmPool, WarmPool } from 'cf-container-warm-pool';

// Extend Sandbox with optional (but recommended) onStop handler
export class MySandbox extends Sandbox<Env> {
  async onStop() {
    const pool = getWarmPool(this.env.WARM_POOL);
    await pool.reportStopped(this.ctx.id.toString());
  }
}

export { WarmPool };

export interface Env {
  SANDBOX: DurableObjectNamespace;
  WARM_POOL: DurableObjectNamespace;
  CONTAINER_ID_CACHE: KVNamespace;
}

export default {
  async fetch(request: Request, env: Env) {
    const pool = createWarmPool(env.WARM_POOL, env.SANDBOX, {
      warmTarget: 3,
      idCache: env.CONTAINER_ID_CACHE,
    });

    // Get a pre-warmed sandbox by session ID
    const sessionId = request.headers.get('x-session-id') || 'default';
    const sandbox = await pool.getContainer(sessionId);

    // Execute code in the sandbox
    const result = await sandbox.exec('python3 -c "print(2 + 2)"');
    return Response.json({ output: result.stdout });
  }
};

Your wrangler.jsonc for Sandbox:

{
  "containers": [
    {
      "class_name": "MySandbox",
      "image": "ghcr.io/cloudflare/sandbox:latest",
      "max_instances": 10
    }
  ],
  "durable_objects": {
    "bindings": [
      { "class_name": "MySandbox", "name": "SANDBOX" },
      { "class_name": "WarmPool", "name": "WARM_POOL" }
    ]
  },
  "kv_namespaces": [
    {
      "binding": "CONTAINER_ID_CACHE",
      "id": "<kv-id>",
      "preview_id": "<kv-preview-id>"
    }
  ],
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["MySandbox"] },
    { "tag": "v2", "new_sqlite_classes": ["WarmPool"] }
  ]
}

Tradeoffs

Increased cost. Pre-warmed containers are billed while sitting idle in the pool. The more containers you keep warm (warmTarget), the higher your baseline cost. Consider your traffic patterns - if you have steady traffic, warm containers get used quickly. If traffic is bursty, you may pay for idle time between bursts.

Auto-generated container IDs. The pool generates UUIDs for container instances rather than using predictable names. This can make observability slightly harder since you can't easily correlate a container ID to a specific user session from logs alone. The pool maintains the mapping between your user-provided IDs and container UUIDs, but this mapping is internal to the pool's storage.

License

MIT