update skills

This commit is contained in:
2026-03-17 16:53:22 -07:00
parent 0b0783ef8e
commit f9a530667e
389 changed files with 54512 additions and 1 deletions

View File

@@ -0,0 +1,85 @@
# Cloudflare Containers Skill Reference
**APPLIES TO: Cloudflare Containers ONLY - NOT general Cloudflare Workers**
Use when working with Cloudflare Containers: deploying containerized apps on Workers platform, configuring container-enabled Durable Objects, managing container lifecycle, or implementing stateful/stateless container patterns.
## Beta Status
⚠️ Containers is currently in **beta**. API may change without notice. No SLA guarantees. Custom instance types added Jan 2026.
## Core Concepts
**Container as Durable Object:** Each container is a Durable Object with persistent identity. Accessed via `getByName(id)` or `getRandom()`.
**Image deployment:** Images pre-fetched globally. Deployments use rolling strategy (not instant like Workers).
**Lifecycle:** cold start (2-3s) → running → `sleepAfter` timeout → stopped. No autoscaling - manual load balancing via `getRandom()`.
**Persistent identity, ephemeral disk:** Container ID persists, but disk resets on stop. Use Durable Object storage for persistence.
## Quick Start
```typescript
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
sleepAfter = "30m";
}
export default {
async fetch(request: Request, env: Env) {
const container = env.MY_CONTAINER.getByName("instance-1");
await container.startAndWaitForPorts();
return container.fetch(request);
}
};
```
## Reading Order
| Task | Files |
|------|-------|
| Setup new container project | README → configuration.md |
| Implement container logic | README → api.md → patterns.md |
| Choose routing pattern | patterns.md (routing section) |
| Debug issues | gotchas.md |
| Production hardening | gotchas.md → patterns.md (lifecycle) |
## Routing Decision Tree
**How should requests reach containers?**
- **Same user/session → same container:** Use `getByName(sessionId)` for session affinity
- **Stateless, spread load:** Use `getRandom()` for load balancing
- **Job per container:** Use `getByName(jobId)` + explicit lifecycle management
- **Single global instance:** Use `getByName("singleton")`
## When to Use Containers vs Workers
**Use Containers when:**
- Need stateful, long-lived processes (sessions, WebSockets, games)
- Running existing containerized apps (Node.js, Python, custom binaries)
- Need filesystem access or specific system dependencies
- Per-user/session isolation with dedicated compute
**Use Workers when:**
- Stateless HTTP handlers
- Sub-millisecond cold starts required
- Auto-scaling to zero critical
- Simple request/response patterns
## In This Reference
- **[configuration.md](configuration.md)** - Wrangler config, instance types, Container class properties, environment variables, account limits
- **[api.md](api.md)** - Container class API, startup methods, communication (HTTP/TCP/WebSocket), routing helpers, lifecycle hooks, scheduling, state inspection
- **[patterns.md](patterns.md)** - Routing patterns (session affinity, load balancing, singleton), WebSocket forwarding, graceful shutdown, Workflow/Queue integration
- **[gotchas.md](gotchas.md)** - Critical gotchas (WebSocket, startup methods), common errors with solutions, specific limits, beta caveats
## See Also
- [Durable Objects](../durable-objects/) - Containers extend Durable Objects
- [Workflows](../workflows/) - Orchestrate container operations
- [Queues](../queues/) - Trigger containers from queue messages
- [Cloudflare Docs](https://developers.cloudflare.com/containers/)

View File

@@ -0,0 +1,187 @@
## Container Class API
```typescript
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
requiredPorts = [8080];
sleepAfter = "30m";
enableInternet = true;
pingEndpoint = "/health";
envVars = {};
entrypoint = [];
onStart() { /* container started */ }
onStop() { /* container stopping */ }
onError(error: Error) { /* container error */ }
onActivityExpired(): boolean { /* timeout, return true to stay alive */ }
async alarm() { /* scheduled task */ }
}
```
## Routing
**getByName(id)** - Named instance for session affinity, per-user state
**getRandom()** - Random instance for load balancing stateless services
```typescript
const container = env.MY_CONTAINER.getByName("user-123");
const container = env.MY_CONTAINER.getRandom();
```
## Startup Methods
### start() - Basic start (8s timeout)
```typescript
await container.start();
await container.start({ envVars: { KEY: "value" } });
```
Returns when **process starts**, NOT when ports ready. Use for fire-and-forget.
### startAndWaitForPorts() - Recommended (20s timeout)
```typescript
await container.startAndWaitForPorts(); // Uses requiredPorts
await container.startAndWaitForPorts({ ports: [8080, 9090] });
await container.startAndWaitForPorts({
ports: [8080],
startOptions: { envVars: { KEY: "value" } }
});
```
Returns when **ports listening**. Use before HTTP/TCP requests.
**Port resolution:** explicit ports → requiredPorts → defaultPort → port 33
### waitForPort() - Wait for specific port
```typescript
await container.waitForPort(8080);
await container.waitForPort(8080, { timeout: 30000 });
```
## Communication
### fetch() - HTTP with WebSocket support
```typescript
// ✅ Supports WebSocket upgrades
const response = await container.fetch(request);
const response = await container.fetch("http://container/api", {
method: "POST",
body: JSON.stringify({ data: "value" })
});
```
**Use for:** All HTTP, especially WebSocket.
### containerFetch() - HTTP only (no WebSocket)
```typescript
// ❌ No WebSocket support
const response = await container.containerFetch(request);
```
**⚠️ Critical:** Use `fetch()` for WebSocket, not `containerFetch()`.
### TCP Connections
```typescript
const port = this.ctx.container.getTcpPort(8080);
const conn = port.connect();
await conn.opened;
if (request.body) await request.body.pipeTo(conn.writable);
return new Response(conn.readable);
```
### switchPort() - Change default port
```typescript
this.switchPort(8081); // Subsequent fetch() uses this port
```
## Lifecycle Hooks
### onStart()
Called when container process starts (ports may not be ready). Runs in `blockConcurrencyWhile` - no concurrent requests.
```typescript
onStart() {
console.log("Container starting");
}
```
### onStop()
Called when SIGTERM received. 15 minutes until SIGKILL. Use for graceful shutdown.
```typescript
onStop() {
// Save state, close connections, flush logs
}
```
### onError()
Called when container crashes or fails to start.
```typescript
onError(error: Error) {
console.error("Container error:", error);
}
```
### onActivityExpired()
Called when `sleepAfter` timeout reached. Return `true` to stay alive, `false` to stop.
```typescript
onActivityExpired(): boolean {
if (this.hasActiveConnections()) return true; // Keep alive
return false; // OK to stop
}
```
## Scheduling
```typescript
export class ScheduledContainer extends Container {
async fetch(request: Request) {
await this.schedule(Date.now() + 60000); // 1 minute
await this.schedule("2026-01-28T00:00:00Z"); // ISO string
return new Response("Scheduled");
}
async alarm() {
// Called when schedule fires (SQLite-backed, survives restarts)
}
}
```
**⚠️ Don't override `alarm()` directly when using `schedule()` helper.**
## State Inspection
### External state check
```typescript
const state = await container.getState();
// state.status: "starting" | "running" | "stopping" | "stopped"
```
### Internal state check
```typescript
export class MyContainer extends Container {
async fetch(request: Request) {
if (this.ctx.container.running) { ... }
}
}
```
**⚠️ Use `getState()` for external checks, `ctx.container.running` for internal.**

View File

@@ -0,0 +1,188 @@
## Wrangler Configuration
### Basic Container Config
```jsonc
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2026-01-10",
"containers": [
{
"class_name": "MyContainer",
"image": "./Dockerfile", // Path to Dockerfile or directory with Dockerfile
"instance_type": "standard-1", // Predefined or custom (see below)
"max_instances": 10
}
],
"durable_objects": {
"bindings": [
{
"name": "MY_CONTAINER",
"class_name": "MyContainer"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MyContainer"] // Must use new_sqlite_classes
}
]
}
```
Key config requirements:
- `image` - Path to Dockerfile or directory containing Dockerfile
- `class_name` - Must match Container class export name
- `max_instances` - Max concurrent container instances
- Must configure Durable Objects binding AND migrations
### Instance Types
#### Predefined Types
| Type | vCPU | Memory | Disk |
|------|------|--------|------|
| lite | 1/16 | 256 MiB | 2 GB |
| basic | 1/4 | 1 GiB | 4 GB |
| standard-1 | 1/2 | 4 GiB | 8 GB |
| standard-2 | 1 | 6 GiB | 12 GB |
| standard-3 | 2 | 8 GiB | 16 GB |
| standard-4 | 4 | 12 GiB | 20 GB |
```jsonc
{
"containers": [
{
"class_name": "MyContainer",
"image": "./Dockerfile",
"instance_type": "standard-2" // Use predefined type
}
]
}
```
#### Custom Types (Jan 2026 Feature)
```jsonc
{
"containers": [
{
"class_name": "MyContainer",
"image": "./Dockerfile",
"instance_type_custom": {
"vcpu": 2, // 1-4 vCPU
"memory_mib": 8192, // 512-12288 MiB (up to 12 GiB)
"disk_mib": 16384 // 2048-20480 MiB (up to 20 GB)
}
}
]
}
```
**Custom type constraints:**
- Minimum 3 GiB memory per vCPU
- Maximum 2 GB disk per 1 GiB memory
- Max 4 vCPU, 12 GiB memory, 20 GB disk per container
### Account Limits
| Resource | Limit | Notes |
|----------|-------|-------|
| Total memory (all containers) | 400 GiB | Across all running containers |
| Total vCPU (all containers) | 100 | Across all running containers |
| Total disk (all containers) | 2 TB | Across all running containers |
| Image storage per account | 50 GB | Stored container images |
### Container Class Properties
```typescript
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
// Port Configuration
defaultPort = 8080; // Default port for fetch() calls
requiredPorts = [8080, 9090]; // Ports to wait for in startAndWaitForPorts()
// Lifecycle
sleepAfter = "30m"; // Inactivity timeout (5m, 30m, 2h, etc.)
// Network
enableInternet = true; // Allow outbound internet access
// Health Check
pingEndpoint = "/health"; // Health check endpoint path
// Environment
envVars = { // Environment variables passed to container
NODE_ENV: "production",
LOG_LEVEL: "info"
};
// Startup
entrypoint = ["/bin/start.sh"]; // Override image entrypoint (optional)
}
```
**Property details:**
- **`defaultPort`**: Port used when calling `container.fetch()` without explicit port. Falls back to port 33 if not set.
- **`requiredPorts`**: Array of ports that must be listening before `startAndWaitForPorts()` returns. First port becomes default if `defaultPort` not set.
- **`sleepAfter`**: Duration string (e.g., "5m", "30m", "2h"). Container stops after this period of inactivity. Timer resets on each request.
- **`enableInternet`**: Boolean. If `true`, container can make outbound HTTP/TCP requests.
- **`pingEndpoint`**: Path used for health checks. Should respond with 2xx status.
- **`envVars`**: Object of environment variables. Merged with runtime-provided vars (see below).
- **`entrypoint`**: Array of strings. Overrides container image's CMD/ENTRYPOINT.
### Runtime Environment Variables
Cloudflare automatically provides these environment variables to containers:
| Variable | Description |
|----------|-------------|
| `CLOUDFLARE_APPLICATION_ID` | Worker application ID |
| `CLOUDFLARE_COUNTRY_A2` | Two-letter country code of request origin |
| `CLOUDFLARE_LOCATION` | Cloudflare data center location |
| `CLOUDFLARE_REGION` | Region identifier |
| `CLOUDFLARE_DURABLE_OBJECT_ID` | Container's Durable Object ID |
Custom `envVars` from Container class are merged with these. Custom vars override runtime vars if names conflict.
### Image Management
**Distribution model:** Images pre-fetched to all global locations before deployment. Ensures fast cold starts (2-3s typical).
**Rolling deploys:** Unlike Workers (instant), container deployments roll out gradually. Old versions continue running during rollout.
**Ephemeral disk:** Container disk is ephemeral and resets on each stop. Use Durable Object storage (`this.ctx.storage`) for persistence.
## wrangler.toml Format
```toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2026-01-10"
[[containers]]
class_name = "MyContainer"
image = "./Dockerfile"
instance_type = "standard-2"
max_instances = 10
[[durable_objects.bindings]]
name = "MY_CONTAINER"
class_name = "MyContainer"
[[migrations]]
tag = "v1"
new_sqlite_classes = ["MyContainer"]
```
Both `wrangler.jsonc` and `wrangler.toml` are supported. Use `wrangler.jsonc` for comments and better IDE support.

View File

@@ -0,0 +1,178 @@
## Critical Gotchas
### ⚠️ WebSocket: fetch() vs containerFetch()
**Problem:** WebSocket connections fail silently
**Cause:** `containerFetch()` doesn't support WebSocket upgrades
**Fix:** Always use `fetch()` for WebSocket
```typescript
// ❌ WRONG
return container.containerFetch(request);
// ✅ CORRECT
return container.fetch(request);
```
### ⚠️ startAndWaitForPorts() vs start()
**Problem:** "connection refused" after `start()`
**Cause:** `start()` returns when process starts, NOT when ports ready
**Fix:** Use `startAndWaitForPorts()` before requests
```typescript
// ❌ WRONG
await container.start();
return container.fetch(request);
// ✅ CORRECT
await container.startAndWaitForPorts();
return container.fetch(request);
```
### ⚠️ Activity Timeout on Long Operations
**Problem:** Container stops during long work
**Cause:** `sleepAfter` based on request activity, not internal work
**Fix:** Renew timeout by touching storage
```typescript
const interval = setInterval(() => {
this.ctx.storage.put("keepalive", Date.now());
}, 60000);
try {
await this.doLongWork(data);
} finally {
clearInterval(interval);
}
```
### ⚠️ blockConcurrencyWhile for Startup
**Problem:** Race conditions during initialization
**Fix:** Use `blockConcurrencyWhile` for atomic initialization
```typescript
await this.ctx.blockConcurrencyWhile(async () => {
if (!this.initialized) {
await this.startAndWaitForPorts();
this.initialized = true;
}
});
```
### ⚠️ Lifecycle Hooks Block Requests
**Problem:** Container unresponsive during `onStart()`
**Cause:** Hooks run in `blockConcurrencyWhile` - no concurrent requests
**Fix:** Keep hooks fast, avoid long operations
### ⚠️ Don't Override alarm() When Using schedule()
**Problem:** Scheduled tasks don't execute
**Cause:** `schedule()` uses `alarm()` internally
**Fix:** Implement `alarm()` to handle scheduled tasks
## Common Errors
### "Container start timeout"
**Cause:** Container took >8s (`start()`) or >20s (`startAndWaitForPorts()`)
**Solutions:**
- Optimize image (smaller base, fewer layers)
- Check `entrypoint` correct
- Verify app listens on correct ports
- Increase timeout if needed
### "Port not available"
**Cause:** Calling `fetch()` before port ready
**Solution:** Use `startAndWaitForPorts()`
### "Container memory exceeded"
**Cause:** Using more memory than instance type allows
**Solutions:**
- Use larger instance type (standard-2, standard-3, standard-4)
- Optimize app memory usage
- Use custom instance type
```jsonc
"instance_type_custom": {
"vcpu": 2,
"memory_mib": 8192
}
```
### "Max instances reached"
**Cause:** All `max_instances` slots in use
**Solutions:**
- Increase `max_instances`
- Implement proper `sleepAfter`
- Use `getRandom()` for distribution
- Check for instance leaks
### "No container instance available"
**Cause:** Account capacity limits reached
**Solutions:**
- Check account limits
- Review instance types across containers
- Contact Cloudflare support
## Limits
| Resource | Limit | Notes |
|----------|-------|-------|
| Cold start | 2-3s | Image pre-fetched globally |
| Graceful shutdown | 15 min | SIGTERM → SIGKILL |
| `start()` timeout | 8s | Process start |
| `startAndWaitForPorts()` timeout | 20s | Port ready |
| Max vCPU per container | 4 | standard-4 or custom |
| Max memory per container | 12 GiB | standard-4 or custom |
| Max disk per container | 20 GB | Ephemeral, resets |
| Account total memory | 400 GiB | All containers |
| Account total vCPU | 100 | All containers |
| Account total disk | 2 TB | All containers |
| Image storage | 50 GB | Per account |
| Disk persistence | None | Use DO storage |
## Best Practices
1. **Use `startAndWaitForPorts()` by default** - Prevents port errors
2. **Set appropriate `sleepAfter`** - Balance resources vs cold starts
3. **Use `fetch()` for WebSocket** - Not `containerFetch()`
4. **Design for restarts** - Ephemeral disk, implement graceful shutdown
5. **Monitor resources** - Stay within account limits
6. **Keep hooks fast** - Run in `blockConcurrencyWhile`
7. **Renew activity for long ops** - Touch storage to prevent timeout
## Beta Caveats
⚠️ Containers in **beta**:
- **API may change** without notice
- **No SLA** guarantees
- **Limited regions** initially
- **No autoscaling** - manual via `getRandom()`
- **Rolling deploys** only (not instant like Workers)
Plan for API changes, test thoroughly before production.

View File

@@ -0,0 +1,202 @@
## Routing Patterns
### Session Affinity (Stateful)
```typescript
export class SessionBackend extends Container {
defaultPort = 3000;
sleepAfter = "30m";
}
export default {
async fetch(request: Request, env: Env) {
const sessionId = request.headers.get("X-Session-ID") || crypto.randomUUID();
const container = env.SESSION_BACKEND.getByName(sessionId);
await container.startAndWaitForPorts();
return container.fetch(request);
}
};
```
**Use:** User sessions, WebSocket, stateful games, per-user caching.
### Load Balancing (Stateless)
```typescript
export default {
async fetch(request: Request, env: Env) {
const container = env.STATELESS_API.getRandom();
await container.startAndWaitForPorts();
return container.fetch(request);
}
};
```
**Use:** Stateless HTTP APIs, CPU-intensive work, read-only queries.
### Singleton Pattern
```typescript
export default {
async fetch(request: Request, env: Env) {
const container = env.GLOBAL_SERVICE.getByName("singleton");
await container.startAndWaitForPorts();
return container.fetch(request);
}
};
```
**Use:** Global cache, centralized coordinator, single source of truth.
## WebSocket Forwarding
```typescript
export default {
async fetch(request: Request, env: Env) {
if (request.headers.get("Upgrade") === "websocket") {
const sessionId = request.headers.get("X-Session-ID") || crypto.randomUUID();
const container = env.WS_BACKEND.getByName(sessionId);
await container.startAndWaitForPorts();
// ⚠️ MUST use fetch(), not containerFetch()
return container.fetch(request);
}
return new Response("Not a WebSocket request", { status: 400 });
}
};
```
**⚠️ Critical:** Always use `fetch()` for WebSocket.
## Graceful Shutdown
```typescript
export class GracefulContainer extends Container {
private connections = new Set<WebSocket>();
onStop() {
// SIGTERM received, 15 minutes until SIGKILL
for (const ws of this.connections) {
ws.close(1001, "Server shutting down");
}
this.ctx.storage.put("shutdown-time", Date.now());
}
onActivityExpired(): boolean {
return this.connections.size > 0; // Keep alive if connections
}
}
```
## Concurrent Request Handling
```typescript
export class SafeContainer extends Container {
private initialized = false;
async fetch(request: Request) {
await this.ctx.blockConcurrencyWhile(async () => {
if (!this.initialized) {
await this.startAndWaitForPorts();
this.initialized = true;
}
});
return super.fetch(request);
}
}
```
**Use:** One-time initialization, preventing concurrent startup.
## Activity Timeout Renewal
```typescript
export class LongRunningContainer extends Container {
sleepAfter = "5m";
async processLongJob(data: unknown) {
const interval = setInterval(() => {
this.ctx.storage.put("keepalive", Date.now());
}, 60000);
try {
await this.doLongWork(data);
} finally {
clearInterval(interval);
}
}
}
```
**Use:** Long operations exceeding `sleepAfter`.
## Multiple Port Routing
```typescript
export class MultiPortContainer extends Container {
requiredPorts = [8080, 8081, 9090];
async fetch(request: Request) {
const path = new URL(request.url).pathname;
if (path.startsWith("/grpc")) this.switchPort(8081);
else if (path.startsWith("/metrics")) this.switchPort(9090);
return super.fetch(request);
}
}
```
**Use:** Multi-protocol services (HTTP + gRPC), separate metrics endpoints.
## Workflow Integration
```typescript
import { WorkflowEntrypoint } from "cloudflare:workers";
export class ProcessingWorkflow extends WorkflowEntrypoint {
async run(event, step) {
const container = this.env.PROCESSOR.getByName(event.payload.jobId);
await step.do("start", async () => {
await container.startAndWaitForPorts();
});
const result = await step.do("process", async () => {
return container.fetch("/process", {
method: "POST",
body: JSON.stringify(event.payload.data)
}).then(r => r.json());
});
return result;
}
}
```
**Use:** Orchestrating multi-step container operations, durable execution.
## Queue Consumer Integration
```typescript
export default {
async queue(batch, env) {
for (const msg of batch.messages) {
try {
const container = env.PROCESSOR.getByName(msg.body.jobId);
await container.startAndWaitForPorts();
const response = await container.fetch("/process", {
method: "POST",
body: JSON.stringify(msg.body)
});
response.ok ? msg.ack() : msg.retry();
} catch (err) {
console.error("Queue processing error:", err);
msg.retry();
}
}
}
};
```
**Use:** Asynchronous job processing, batch operations, event-driven execution.