来自 @cloudflare/containers ↗ 的 Container 类 ↗是从 Worker 与容器实例进行交互的最常见方式。
Container 扩展了 DurableObject。 Durable Object 管理路由、持久状态和生命周期钩子,而容器进程在 Linux VM 中运行您的镜像。由于您的子类是 Durable Object,因此您可以访问完整的 Durable Object API——包括用于支持 SQLite 的持久存储的 this.ctx.storage 以及唯一实例标识符的 this.ctx.id。使用 Durable Object 存储来持久化在容器重启后应保留的状态,例如配置、用户数据或任务结果。
npm i @cloudflare/containersyarn add @cloudflare/containerspnpm add @cloudflare/containersbun add @cloudflare/containers然后,定义一个扩展 Container 的类,并在该类上设置共享属性:
import { Container, getContainer } from "@cloudflare/containers";
export class SandboxContainer extends Container {
defaultPort = 8080;
requiredPorts = [8080, 9222];
sleepAfter = "5m";
envVars = {
NODE_ENV: "production",
LOG_LEVEL: "info",
};
entrypoint = ["npm", "run", "start"];
enableInternet = false;
pingEndpoint = "localhost/ready";
}
export default {
async fetch(request, env) {
return getContainer(env.SANDBOX_CONTAINER, "workspace-123").fetch(request);
},
};import { Container, getContainer } from "@cloudflare/containers";
export class SandboxContainer extends Container {
defaultPort = 8080;
requiredPorts = [8080, 9222];
sleepAfter = "5m";
envVars = {
NODE_ENV: "production",
LOG_LEVEL: "info",
};
entrypoint = ["npm", "run", "start"];
enableInternet = false;
pingEndpoint = "localhost/ready";
}
export default {
async fetch(request: Request, env) {
return getContainer(env.SANDBOX_CONTAINER, "workspace-123").fetch(request);
},
};Container 类扩展了 DurableObject,因此可以使用所有 Durable Object 功能——包括 SQLite 存储、警报 (alarms) 和 RPC 方法。容器磁盘默认是临时的,但 Durable Object 存储在容器重启后仍会保留。
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
async runAndPersist() {
const res = await this.containerFetch("/run-task");
const body = await res.text();
this.ctx.storage.sql.exec(
"INSERT OR REPLACE INTO results (value) VALUES (?)",
body,
);
return body;
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
async runAndPersist() {
const res = await this.containerFetch("/run-task");
const body = await res.text();
this.ctx.storage.sql.exec(
"INSERT OR REPLACE INTO results (value) VALUES (?)",
body,
);
return body;
}
}使用 this.ctx.container.exec() 在运行中的容器内启动另一个进程。有关启动、流式传输、输出和进程控制示例,请参阅执行命令。
在子类中将这些配置为类字段。它们适用于容器的每个实例。
defaultPort(number,可选) — 您容器进程侦听的端口。除非您通过switchPort()或containerFetch()的port参数指定了不同的端口,否则fetch()和containerFetch()将请求转发到此处。大多数子类都会设置它。requiredPorts(number[],可选) — 在容器被视为就绪之前必须接受连接的端口。未传递ports参数时,由startAndWaitForPorts()使用。当您的容器运行多个服务且所有服务在提供流量前都需要健康运行时,请设置此项。sleepAfter(string | number,默认:"10m") — 容器在没有活动的情况下保持活动多长时间后将其关闭。接受秒数或持续时间字符串,例如"30s"、"5m"或"1h"。活动会重置计时器 — 见renewActivityTimeout()进行手动重置。envVars(Record<string, string>,默认:{}) — 每次启动时传递给容器的环境变量。对于每个实例的变量,请通过startAndWaitForPorts()传递envVars。entrypoint(string[],可选) — 覆盖镜像的默认入口点。当您想要在不重建镜像的情况下运行不同的命令(例如开发服务器或一次性任务)时非常有用。enableInternet(boolean,默认:true) — 控制容器是否可以发出出站 HTTP 请求。对于想要拦截或阻止所有出站流量的沙盒环境,请设置为false。有关更多信息,请参阅处理出站流量。pingEndpoint(string,默认:"ping") — 该类在启动期间用于对容器进行运行状况检查的主机和路径。大多数用户无需更改此设置。
覆盖这些方法以在容器状态改变时运行 Worker 代码。有关完整示例,请参阅状态钩子示例。
容器启动后运行 Worker 代码。
onStart(): void | Promise<void>返回: void | Promise<void>。在任何启动逻辑完成后解析。
使用它来记录启动、种子数据,或通过 schedule() 调度定期任务。
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
async onStart() {
await this.containerFetch("http://localhost/bootstrap", {
method: "POST",
});
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
override async onStart() {
await this.containerFetch("http://localhost/bootstrap", {
method: "POST",
});
}
}在容器进程退出后运行 Worker 代码。
onStop(params: StopParams): void | Promise<void>参数:
params.exitCode- 容器进程退出码。params.reason- 容器停止的原因:当进程自行退出时为'exit',或者当运行时向它发出信号时为'runtime_signal'。
返回: void | Promise<void>。在您的关闭逻辑完成后解析。
使用此选项可对容器进行日志记录、发出警报或重启。
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
onStop({ exitCode, reason }) {
console.log("Container stopped", { exitCode, reason });
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
override onStop({ exitCode, reason }) {
console.log("Container stopped", { exitCode, reason });
}
}处理启动和端口检查错误。
onError(error: unknown): any参数:
error- 在启动或端口检查期间引发的错误。
返回: any。默认实现记录错误并重新抛出它。
覆盖它以抑制错误、通知外部服务或尝试重启。
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
onError(error) {
console.error("Container failed to start", error);
throw error;
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
override onError(error: unknown) {
console.error("Container failed to start", error);
throw error;
}
}在 sleepAfter 计时器到期时运行 Worker 代码。
onActivityExpired(): Promise<void>返回: Promise<void>。在空闲时间逻辑完成后解析。
当 sleepAfter 超时且没有传入请求时调用。默认实现调用 stop()。
如果在不停止容器的情况下覆盖此方法,计时器将更新,并且钩子将在下次到期时再次触发。
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
sleepAfter = "2m";
async onActivityExpired() {
const state = await this.getState();
console.log("Container is idle, stopping it now", state.status);
await this.stop();
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
sleepAfter = "2m";
override async onActivityExpired() {
const state = await this.getState();
console.log("Container is idle, stopping it now", state.status);
await this.stop();
}
}处理传入的 HTTP 或 WebSocket 请求。
fetch(request: Request): Promise<Response>参数:
request- 代理到容器的传入请求。
返回: 来自容器或来自您的自定义路由逻辑的 Promise<Response>。
默认情况下,fetch 会将请求转发到位于 defaultPort 的容器进程。如果容器尚未运行,它将自动启动。
在转发到容器之前,如果您需要路由逻辑、身份验证或其他中间件,请覆盖 fetch。在覆盖内,调用 this.containerFetch() 而不是 this.fetch(),以避免无限递归:
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/health") {
return new Response("ok");
}
return this.containerFetch(request);
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
override async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/health") {
return new Response("ok");
}
return this.containerFetch(request);
}
}fetch 是支持 WebSocket 代理的唯一方法。有关完整示例,请参阅 WebSocket 示例。
直接向容器进程发送 HTTP 请求。通常,用户应更喜欢使用 fetch,除非它已被覆盖。
containerFetch(request: Request, port?: number): Promise<Response>
containerFetch(url: string | URL, init?: RequestInit, port?: number): Promise<Response>参数:
request- 要转发的现有Request对象。url- 构造新请求时要请求的 URL。init- 基于 URL 重载的标准RequestInit选项。port- 可选的目标端口。如果省略,该类使用defaultPort。
返回: 来自容器的 Promise<Response>。
这是默认 fetch() 实现内部调用的方法,为了避免无限递归,它也是您应该在被覆盖的 fetch() 方法中调用的方法。它也接受一个标准的 fetch 样式签名(带有 URL 字符串和 RequestInit),当您在构造新请求而不是转发现有请求时,这非常有用。
不支持 WebSocket。对于 WebSocket,请使用带有 switchPort() 的 fetch()。
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/metrics") {
return this.containerFetch(
"http://localhost/internal/metrics",
{
headers: {
authorization: request.headers.get("authorization") ?? "",
},
},
9090,
);
}
return this.containerFetch(request);
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
override async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/metrics") {
return this.containerFetch(
"http://localhost/internal/metrics",
{
headers: {
authorization: request.headers.get("authorization") ?? "",
},
},
9090,
);
}
return this.containerFetch(request);
}
}在大多数情况下,您不需要直接调用这些方法。fetch() 和 containerFetch() 自动启动容器。当您需要预热容器、按计划运行任务或从生命周期钩子内部控制生命周期时,显式调用这些方法。
启动容器并等待,直到目标端口接受连接。
startAndWaitForPorts(args?: StartAndWaitForPortsOptions): Promise<void>
startAndWaitForPorts(
ports?: number | number[],
cancellationOptions?: CancellationOptions,
startOptions?: ContainerStartConfigOptions,
): Promise<void>参数:
args.ports- 要等待的一个或多个端口。端口解析顺序是显式的ports,然后是requiredPorts,最后是defaultPort。args.startOptions- 每个实例的启动覆盖。args.startOptions.envVars- 每个实例的环境变量。args.startOptions.entrypoint- 仅用于本次启动的入口点覆盖。args.startOptions.enableInternet- 此次启动是否允许出站互联网访问。args.cancellationOptions.abort- 取消启动的中止信号。args.cancellationOptions.instanceGetTimeoutMS- 获取容器实例并发出 start 命令的最长时间。默认:8000。args.cancellationOptions.portReadyTimeoutMS- 等待所有端口就绪的最长时间。默认:20000。args.cancellationOptions.waitInterval- 轮询间隔(以毫秒为单位)。默认:300。
返回: Promise<void>。在目标端口准备就绪且 onStart() 运行后解析。
当您在发送流量前需要确保容器已准备就绪时,这是显式启动容器的最安全的方法。
此方法还支持按位置传递 ports、cancellationOptions 和 startOptions 参数,但对象形式更易读。
import { getContainer } from "@cloudflare/containers";
export default {
async scheduled(_event, env) {
const container = getContainer(env.API_CONTAINER, "tenant-42");
await container.startAndWaitForPorts({
ports: [8080, 9222],
startOptions: {
envVars: {
API_KEY: env.API_KEY,
TENANT_ID: "tenant-42",
},
},
cancellationOptions: {
portReadyTimeoutMS: 30_000,
},
});
},
};import { getContainer } from "@cloudflare/containers";
export default {
async scheduled(_event, env) {
const container = getContainer(env.API_CONTAINER, "tenant-42");
await container.startAndWaitForPorts({
ports: [8080, 9222],
startOptions: {
envVars: {
API_KEY: env.API_KEY,
TENANT_ID: "tenant-42",
},
},
cancellationOptions: {
portReadyTimeoutMS: 30_000,
},
});
},
};有关完整示例,请参阅 环境变量和机密示例。
启动容器,而不等待所有端口准备就绪。
start(startOptions?: ContainerStartConfigOptions, waitOptions?: WaitOptions): Promise<void>参数:
startOptions- 每个实例的启动覆盖。startOptions.envVars- 每个实例的环境变量。startOptions.entrypoint- 仅用于本次启动的入口点覆盖。startOptions.enableInternet- 此次启动是否允许出站互联网访问。waitOptions.portToCheck- 启动时要探测的端口。如果省略,该类使用defaultPort、第一个requiredPorts条目,或回退端口。waitOptions.signal- 取消启动的中止信号。waitOptions.retries- 方法抛出错误前尝试启动的最大次数。waitOptions.waitInterval- 两次重试之间的轮询间隔(毫秒)。
返回: Promise<void>。在启动尝试成功并且 onStart() 运行后解析。
当容器不暴露端口(例如批处理作业或 cron 任务)时,或者当您希望使用 waitForPort() 自行管理就绪状态时,请使用此项。如果您需要等待所有端口就绪,请使用 startAndWaitForPorts()。
import { getContainer } from "@cloudflare/containers";
export default {
async scheduled(_event, env) {
const container = getContainer(env.JOB_CONTAINER, "nightly-report");
await container.start({
entrypoint: ["node", "scripts/nightly-report.js"],
envVars: {
REPORT_DATE: new Date().toISOString(),
},
enableInternet: false,
});
},
};import { getContainer } from "@cloudflare/containers";
export default {
async scheduled(_event, env) {
const container = getContainer(env.JOB_CONTAINER, "nightly-report");
await container.start({
entrypoint: ["node", "scripts/nightly-report.js"],
envVars: {
REPORT_DATE: new Date().toISOString(),
},
enableInternet: false,
});
},
};有关完整示例,请参阅 cron 示例。
轮询单个端口,直到它接受连接。
waitForPort(waitOptions: WaitOptions): Promise<number>参数:
waitOptions.portToCheck- 要检查的端口号。waitOptions.signal- 取消等待的中止信号。waitOptions.retries- 方法抛出异常之前的最大重试次数。waitOptions.waitInterval- 轮询间隔(毫秒)。
返回: Promise<number>。当您协调跨多个等待的自定义就绪逻辑时,数字返回值主要有用。
如果端口未在重试限制内可用则抛出。当需要在 start() 后独立或以特定顺序检查多个端口时,使用此方法。
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async warmInspector() {
await this.start();
const retryCount = await this.waitForPort({
portToCheck: 9222,
retries: 20,
waitInterval: 500,
});
console.log("Inspector port became ready:", retryCount);
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async warmInspector() {
await this.start();
const retryCount = await this.waitForPort({
portToCheck: 9222,
retries: 20,
waitInterval: 500,
});
console.log("Inspector port became ready:", retryCount);
}
}向容器进程发送信号。
stop(signal?: 'SIGTERM' | 'SIGINT' | 'SIGKILL' | number): Promise<void>参数:
signal- 要发送的信号。默认为'SIGTERM'。
返回: Promise<void>。发送信号且未决的停止处理完成后解析。
默认为 SIGTERM,它使进程有机会优雅地关闭。会触发 onStop()。
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
async fetch(request) {
if (new URL(request.url).pathname === "/admin/stop") {
await this.stop();
return new Response("Container is stopping");
}
return this.containerFetch(request);
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
override async fetch(request: Request): Promise<Response> {
if (new URL(request.url).pathname === "/admin/stop") {
await this.stop();
return new Response("Container is stopping");
}
return this.containerFetch(request);
}
}立即终止容器进程。
destroy(): Promise<void>返回: Promise<void>。在运行时销毁容器后解析。
这将发送 SIGKILL。当您需要立即移除容器并且无法等待其优雅关闭时,请使用此方法。会触发 onStop()。
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
async fetch(request) {
if (new URL(request.url).pathname === "/admin/destroy") {
await this.destroy();
return new Response("Container destroyed");
}
return this.containerFetch(request);
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
override async fetch(request: Request): Promise<Response> {
if (new URL(request.url).pathname === "/admin/destroy") {
await this.destroy();
return new Response("Container destroyed");
}
return this.containerFetch(request);
}
}读取当前的容器状态。
getState(): Promise<State>返回: 带有以下属性的 Promise<State>:
status-'running'、'healthy'、'stopping'、'stopped'或'stopped_with_code'之一。lastChange- 最后一次状态更改的 Unix 时间戳(毫秒)。exitCode- 当status为'stopped_with_code'时的可选退出码。
running 表示容器正在启动,尚未通过运行状况检查。healthy 表示它已启动并接受请求。
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async logState() {
const state = await this.getState();
if (state.status === "stopped_with_code") {
console.error("Container exited with code", state.exitCode);
return;
}
console.log("Container status:", state.status);
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async logState() {
const state = await this.getState();
if (state.status === "stopped_with_code") {
console.error("Container exited with code", state.exitCode);
return;
}
console.log("Container status:", state.status);
}
}重置 sleepAfter 计时器。
renewActivityTimeout(): void返回: void。
传入的请求会自动重置计时器。当您执行后台任务(例如计划任务或长时间运行的操作)时,请手动调用此函数,它将计为活动并防止容器进入休眠状态。
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
async processJobs(jobIds) {
for (const jobId of jobIds) {
this.renewActivityTimeout();
await this.containerFetch(`http://localhost/jobs/${jobId}`, {
method: "POST",
});
}
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
async processJobs(jobIds: string[]) {
for (const jobId of jobIds) {
this.renewActivityTimeout();
await this.containerFetch(`http://localhost/jobs/${jobId}`, {
method: "POST",
});
}
}
}调度该类上的一个方法以便稍后运行。
schedule<T>(when: Date | number, callback: string, payload?: T): Promise<Schedule<T>>参数:
when- 特定时间的Date,或是要延迟的秒数。callback- 要调用的类方法的名称。payload- 传递给回调方法的可选数据。
返回: 带有以下属性的 Promise<Schedule<T>>:
taskId- 唯一的时间表 ID。callback- 将被调用的方法名称。payload- 将传递给回调的有效载荷。type- 针对绝对时间的'scheduled',或针对相对延迟的'delayed'。time- 任务运行时的 Unix 时间戳(以秒为单位)。delayInSeconds- 当type为'delayed'时的延迟(以秒为单位)。
不要直接覆盖 alarm()。Container 类使用 alarm 处理程序来管理容器生命周期,因此请改用 schedule()。
以下示例在容器启动时安排定期运行的健康状况报告:
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
async onStart() {
await this.schedule(60, "healthReport");
}
async healthReport() {
const state = await this.getState();
console.log("Container status:", state.status);
await this.schedule(60, "healthReport");
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
override async onStart() {
await this.schedule(60, "healthReport");
}
async healthReport() {
const state = await this.getState();
console.log("Container status:", state.status);
await this.schedule(60, "healthReport");
}
}出站拦截使您能够拦截、模拟或阻止容器对外部主机发出的 HTTP 请求。这对于通过 Worker 代码对出站流量进行沙箱处理、测试或代理非常有用。
import {
Container,
ContainerProxy,
getContainer,
} from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
enableInternet = true;
static outboundByHost = {
"blocked.example.com": () => {
return new Response("Blocked", { status: 403 });
},
};
static outbound = async (request, _env, ctx) => {
console.log(`[${ctx.containerId}] outbound:`, request.url);
return fetch(request);
};
}
export { ContainerProxy };
export default {
async fetch(request, env) {
return getContainer(env.MY_CONTAINER).fetch(request);
},
};import {
Container,
ContainerProxy,
getContainer,
} from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
enableInternet = true;
static outboundByHost = {
"blocked.example.com": () => {
return new Response("Blocked", { status: 403 });
},
};
static outbound = async (request, _env, ctx) => {
console.log(`[${ctx.containerId}] outbound:`, request.url);
return fetch(request);
};
}
export { ContainerProxy };
export default {
async fetch(request: Request, env) {
return getContainer(env.MY_CONTAINER).fetch(request);
},
};有关更多信息,请参阅处理出站流量。
这些函数与来自 @cloudflare/containers 的 Container 类一起导出。
获取命名容器实例的 stub(存根)。
getContainer<T>(binding: DurableObjectNamespace<T>, name?: string): DurableObjectStub<T>参数:
binding- 用于您容器类的 Durable Object 命名空间绑定。name- 稳定的实例名称。默认为cf-singleton-container。
返回: 命名容器实例的 DurableObjectStub<T>。
当希望每个逻辑实体(例如用户会话、文档或由稳定名称标识的游戏室)都有一个容器时,请使用此选项。
import { getContainer } from "@cloudflare/containers";
export default {
async fetch(request, env) {
const { sessionId } = await request.json();
return getContainer(env.MY_CONTAINER, sessionId).fetch(request);
},
};import { getContainer } from "@cloudflare/containers";
export default {
async fetch(request: Request, env) {
const { sessionId } = await request.json();
return getContainer(env.MY_CONTAINER, sessionId).fetch(request);
},
};获取随机选择的容器实例的 stub(存根)。
getRandom<T>(binding: DurableObjectNamespace<T>, instances?: number): Promise<DurableObjectStub<T>>参数:
binding- 用于您容器类的 Durable Object 命名空间绑定。instances- 可供选择的实例总数。默认为3。
返回: 随机选择的实例的 Promise<DurableObjectStub<T>>。
将此用于无状态工作负载,在这种工作负载中,任何容器都可以处理任何请求,并且您希望跨多个实例分配负载。
import { getRandom } from "@cloudflare/containers";
export default {
async fetch(request, env) {
const container = await getRandom(env.WORKER_POOL, 5);
return container.fetch(request);
},
};import { getRandom } from "@cloudflare/containers";
export default {
async fetch(request: Request, env) {
const container = await getRandom(env.WORKER_POOL, 5);
return container.fetch(request);
},
};有关完整示例,请参阅无状态实例示例。
仍在使用 fetch() 的同时定位不同的容器端口。
switchPort(request: Request, port: number): Request参数:
request- 要复制的请求。port- 要编码到请求标头中的端口。
返回: 设置了目标端口的 Request 副本。
当您需要目标端口也需要支持 WebSocket 时使用此功能。如果不需要 WebSocket,请直接将端口传递给 containerFetch()。
import { getContainer, switchPort } from "@cloudflare/containers";
export default {
async fetch(request, env) {
const container = getContainer(env.MY_CONTAINER);
return container.fetch(switchPort(request, 9090));
},
};import { getContainer, switchPort } from "@cloudflare/containers";
export default {
async fetch(request: Request, env) {
const container = getContainer(env.MY_CONTAINER);
return container.fetch(switchPort(request, 9090));
},
};