Provider-agnostic async container pool with expiry recovery and per-request file tracking
Project description
container-pool
Production-grade async container pool for Python. Handles container lifecycle, reuse, concurrency, and automatic recovery from expiry — so you don't have to.
Built for OpenAI's Code Interpreter, but designed to work with any sandboxed container runtime via a pluggable backend interface.
The Problem
When running sandboxed containers behind a multi-user backend, you hit problems no provider solves for you:
- Containers expire silently after 20 minutes of inactivity. Your next request fails with a 404.
- No built-in pooling. Every request creates a new container (~2-3s overhead).
- No concurrency management. Two users hitting your API simultaneously? You're on your own.
- File cleanup is your problem. Leaked files accumulate and you eat the storage cost.
container-pool is the infrastructure layer that handles all of this.
What This Does
Request A ──→ acquire() ──→ [Container 1] ──→ release() ──→ back to pool
Request B ──→ acquire() ──→ [Container 2] ──→ release() ──→ back to pool
Request C ──→ acquire() ──→ (pool full, blocks until release) ──→ ...
- FIFO pool with configurable size, blocking acquisition with timeout when exhausted
- Automatic expiry recovery — detects expired containers (404, status=expired) and transparently recreates them
- Per-request file tracking with cleanup, so containers stay clean between users
- Retry with exponential backoff on container creation failures
- Graceful shutdown that destroys all containers on exit
- Provider-agnostic — implement
BaseContainerBackendto support any runtime
Installation
pip install container-pool # core only
pip install "container-pool[openai]" # with OpenAI backend
Usage
from openai import AsyncOpenAI
from container_pool import ContainerPool, RequestFileTracker
from container_pool.backends.openai import OpenAIContainerBackend
client = AsyncOpenAI()
backend = OpenAIContainerBackend(client)
pool = ContainerPool(
backend,
max_pool_size=5,
acquire_timeout=30.0,
container_name="my-pool",
)
# Acquire, use, release
container = await pool.acquire()
try:
tracker = RequestFileTracker(container)
uploaded = await tracker.upload_file("/tmp/data.csv")
# ... run code interpreter with container.container_id ...
files = await container.list_output_files("/mnt/data/")
results = await container.download_files(files, "/tmp/output")
finally:
await tracker.cleanup() # delete uploaded files
await pool.release(container) # return to pool
# On app shutdown
await pool.shutdown()
Custom Backends
Implement BaseContainerBackend to plug in any container runtime:
from container_pool import BaseContainerBackend, ContainerInfo, UploadedFile
class MyBackend(BaseContainerBackend):
async def create_container(self, name: str) -> ContainerInfo: ...
async def get_container(self, container_id: str) -> ContainerInfo: ...
async def destroy_container(self, container_id: str) -> None: ...
async def upload_file(self, container_id: str, local_path: str) -> UploadedFile: ...
async def download_file_content(self, container_id: str, file_id: str) -> bytes: ...
async def download_file_to_disk(self, container_id: str, file_id: str, local_path: str) -> int: ...
async def delete_file(self, container_id: str, file_id: str) -> None: ...
async def list_files(self, container_id: str, path_prefix: str = "") -> dict[str, str]: ...
How It Works
Acquire Flow
acquire()
├─ Queue has available container? → validate it's alive → return
├─ Pool below max size? → create new container → return
└─ Pool exhausted? → block until someone calls release() (with timeout)
Expiry Recovery
container-pool handles silent expiry transparently — callers always get a live container:
validate_or_recreate(container)
├─ active status → use it
├─ expired status → recreate
├─ 404 → recreate
└─ connection error → recreate
Performance
| Operation | Latency |
|---|---|
| Warm acquire | <100ms |
| Cold acquire | ~2-3s (container creation) |
| Pool exhausted | Blocks up to acquire_timeout |
| Expiry recovery | ~2-3s (transparent recreation) |
Configuration
| Parameter | Description |
|---|---|
max_pool_size |
Max containers in pool (1–50) |
acquire_timeout |
Seconds to wait when pool is exhausted |
container_name |
Name prefix for created containers |
creation_max_attempts |
Retry attempts on creation failure (default: 3) |
creation_base_delay |
Base delay for exponential backoff in seconds (default: 1.0) |
Roadmap
v1 (current)
- FIFO pool with
asyncio.Queue - Automatic expiry detection and recovery
- Per-request file tracking and cleanup
- Retry with exponential backoff
- Graceful shutdown
- Pluggable backend interface
- OpenAI Code Interpreter backend
v2
- Pool pre-warming — create containers at startup to eliminate cold-start latency
- Background keep-alive — periodic pings to prevent idle expiry
- Distributed state — Redis/PostgreSQL backend for multi-node deployments
- Observability — metrics for pool utilization, acquire wait times, expiry rate
- Pool strategies — LRU, priority-based in addition to FIFO
Contributing
Contributions welcome. Please open an issue first to discuss what you'd like to change.
Why This Exists
Built after hitting every one of these problems while running Code Interpreter in a multi-user production backend. OpenAI's docs hand you a container ID and say good luck — this is the "good luck" part.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file container_pool-0.1.0.tar.gz.
File metadata
- Download URL: container_pool-0.1.0.tar.gz
- Upload date:
- Size: 17.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6d9eabe0db3b36eeadfffe73baa1486d52f32b2f67f52908b8587c894606394
|
|
| MD5 |
6fc836ef0331bc8d23ea857b847edc9c
|
|
| BLAKE2b-256 |
4fee185d26f78a54894ae54d76297fe6c52e0a5e224fc968bcd6c9503084f366
|
Provenance
The following attestation bundles were made for container_pool-0.1.0.tar.gz:
Publisher:
publish.yml on aayushdwids/container-pool
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
container_pool-0.1.0.tar.gz -
Subject digest:
c6d9eabe0db3b36eeadfffe73baa1486d52f32b2f67f52908b8587c894606394 - Sigstore transparency entry: 975675550
- Sigstore integration time:
-
Permalink:
aayushdwids/container-pool@f7e77d6407c6d5935363492a3c628487ea967f8a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/aayushdwids
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f7e77d6407c6d5935363492a3c628487ea967f8a -
Trigger Event:
release
-
Statement type:
File details
Details for the file container_pool-0.1.0-py3-none-any.whl.
File metadata
- Download URL: container_pool-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c6cbb8ce05b5d4784bbb12dc8817163063bad41565465c7cb0dde76d8e1ec84
|
|
| MD5 |
f5246771bee8cd6ce4d8d3485c7f7820
|
|
| BLAKE2b-256 |
58a6b1f75efeef38ac7f43d6d821908cf7dced6153f97766f1ef2de8ac75cb13
|
Provenance
The following attestation bundles were made for container_pool-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on aayushdwids/container-pool
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
container_pool-0.1.0-py3-none-any.whl -
Subject digest:
4c6cbb8ce05b5d4784bbb12dc8817163063bad41565465c7cb0dde76d8e1ec84 - Sigstore transparency entry: 975675551
- Sigstore integration time:
-
Permalink:
aayushdwids/container-pool@f7e77d6407c6d5935363492a3c628487ea967f8a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/aayushdwids
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f7e77d6407c6d5935363492a3c628487ea967f8a -
Trigger Event:
release
-
Statement type: