Files
2026-03-17 16:53:22 -07:00

4.2 KiB

Container Class API

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

const container = env.MY_CONTAINER.getByName("user-123");
const container = env.MY_CONTAINER.getRandom();

Startup Methods

start() - Basic start (8s timeout)

await container.start();
await container.start({ envVars: { KEY: "value" } });

Returns when process starts, NOT when ports ready. Use for fire-and-forget.

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

await container.waitForPort(8080);
await container.waitForPort(8080, { timeout: 30000 });

Communication

fetch() - HTTP with WebSocket support

// ✅ 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)

// ❌ No WebSocket support
const response = await container.containerFetch(request);

⚠️ Critical: Use fetch() for WebSocket, not containerFetch().

TCP Connections

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

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.

onStart() {
  console.log("Container starting");
}

onStop()

Called when SIGTERM received. 15 minutes until SIGKILL. Use for graceful shutdown.

onStop() {
  // Save state, close connections, flush logs
}

onError()

Called when container crashes or fails to start.

onError(error: Error) {
  console.error("Container error:", error);
}

onActivityExpired()

Called when sleepAfter timeout reached. Return true to stay alive, false to stop.

onActivityExpired(): boolean {
  if (this.hasActiveConnections()) return true;  // Keep alive
  return false;  // OK to stop
}

Scheduling

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

const state = await container.getState();
// state.status: "starting" | "running" | "stopping" | "stopped"

Internal state check

export class MyContainer extends Container {
  async fetch(request: Request) {
    if (this.ctx.container.running) { ... }
  }
}

⚠️ Use getState() for external checks, ctx.container.running for internal.