4.4 KiB
Critical Gotchas
⚠️ WebSocket: fetch() vs containerFetch()
Problem: WebSocket connections fail silently
Cause: containerFetch() doesn't support WebSocket upgrades
Fix: Always use fetch() for WebSocket
// ❌ 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
// ❌ 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
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
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
entrypointcorrect - 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
"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
- Use
startAndWaitForPorts()by default - Prevents port errors - Set appropriate
sleepAfter- Balance resources vs cold starts - Use
fetch()for WebSocket - NotcontainerFetch() - Design for restarts - Ephemeral disk, implement graceful shutdown
- Monitor resources - Stay within account limits
- Keep hooks fast - Run in
blockConcurrencyWhile - 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.