Execute commands·续

return new TextDecoder().decode(output.stdout); } }


```ts
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
    async readCombinedOutput() {
        if (!this.ctx.container.running) {
            await this.start();
        }

const process = await this.ctx.container.exec(
            [
                "bash",
                "-lc",
                'printf "standard output\n"; printf "standard error\n" >&2',
            ],
            {
                stdout: "pipe",
                stderr: "combined",
            },
        );
        const output = await process.output();

return new TextDecoder().decode(output.stdout);
    }
}

The merged stream does not guarantee ordering between source streams. In this mode, process.stderr is undefined, and output.stderr is an empty ArrayBuffer. This example assumes Bash exists in the image.

Handle nonzero exits

A nonzero exit code resolves exitCode normally. It does not reject the promise.

This example preserves standard error while ignoring standard output:

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
    async runCheck() {
        if (!this.ctx.container.running) {
            await this.start();
        }

const process = await this.ctx.container.exec(
            [
                "sh",
                "-c",
                'printf "not captured"; printf "check failed\n" >&2; exit 7',
            ],
            { stdout: "ignore" },
        );
        const output = await process.output();

return {
            exitCode: output.exitCode,
            stdoutBytes: output.stdout.byteLength,
            stderr: new TextDecoder().decode(output.stderr),
        };
    }
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
    async runCheck() {
        if (!this.ctx.container.running) {
            await this.start();
        }

const process = await this.ctx.container.exec(
        [
            "sh",
            "-c",
            'printf "not captured"; printf "check failed\n" >&2; exit 7',
        ],
        { stdout: "ignore" },
    );
    const output = await process.output();

return {
        exitCode: output.exitCode,
        stdoutBytes: output.stdout.byteLength,
        stderr: new TextDecoder().decode(output.stderr),
    };
    }

}

The result contains exit code 7 and the standard error text. Its ignored standard output buffer has zero bytes.

Stream large output

output() buffers both streams in memory. For large output, drain stdout and stderr concurrently instead.

import { Container } from "@cloudflare/containers";

async function countBytes(stream) {
    if (!stream) {
        return 0;
    }

let bytes = 0;
    for await (const chunk of stream) {
        bytes += chunk.byteLength;
    }
    return bytes;
}

export class MyContainer extends Container {
    async generateLargeOutput() {
        if (!this.ctx.container.running) {
            await this.start();
        }

const process = await this.ctx.container.exec([
            "sh",
            "-c",
            'i=0; while [ "$i" -lt 100000 ]; do printf "output %s\n" "$i"; printf "error %s\n" "$i" >&2; i=$((i + 1)); done',
        ]);

const [stdoutBytes, stderrBytes, exitCode] = await Promise.all([
            countBytes(process.stdout),
            countBytes(process.stderr),
            process.exitCode,
        ]);

return { stdoutBytes, stderrBytes, exitCode };
    }
}
import { Container } from "@cloudflare/containers";

async function countBytes(stream: ReadableStream<Uint8Array> | null) {
    if (!stream) {
        return 0;
    }

let bytes = 0;
    for await (const chunk of stream) {
        bytes += chunk.byteLength;
    }
    return bytes;
}

export class MyContainer extends Container {
    async generateLargeOutput() {
        if (!this.ctx.container.running) {
            await this.start();
        }

const process = await this.ctx.container.exec([
            "sh",
            "-c",
            'i=0; while [ "$i" -lt 100000 ]; do printf "output %s\n" "$i"; printf "error %s\n" "$i" >&2; i=$((i + 1)); done',
        ]);

const [stdoutBytes, stderrBytes, exitCode] = await Promise.all([
            countBytes(process.stdout),
            countBytes(process.stderr),
            process.exitCode,
        ]);

return { stdoutBytes, stderrBytes, exitCode };
    }
}

Streaming and output() are alternative consumption methods. output() throws a TypeError if either stream has started being consumed. A second call to output() also throws a TypeError.

Return standard output over RPC

Return a ReadableStream from an RPC method to stream output to the calling Worker. Combining standard error provides one stream for both output channels.

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
    async streamCommandOutput() {
        if (!this.ctx.container.running) {
            await this.start();
        }

const process = await this.ctx.container.exec(
            ["sh", "-c", 'printf "starting\n"; run-report'],
            { stderr: "combined" },
        );

return process.stdout;
    }
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
    async streamCommandOutput(): Promise<ReadableStream<Uint8Array>> {
        if (!this.ctx.container.running) {
            await this.start();
        }

const process = await this.ctx.container.exec(
        ["sh", "-c", 'printf "starting\n"; run-report'],
        { stderr: "combined" },
    );

return process.stdout!;
    }

}

RPC transfers ownership of the stream to the caller and preserves flow control. The caller must consume or cancel the stream. If the caller stops reading, backpressure can pause a process that continues writing.

This method transfers output, not the ExecProcess handle. Define a separate application protocol when the caller needs completion metadata or process control.

Stop a process

exec() has no built-in timeout. You can request termination after a delay with kill() and then await exitCode.

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
    async runWithTimeout() {
        if (!this.ctx.container.running) {
            await this.start();
        }

const process = await this.ctx.container.exec(["sleep", "120"]);
        const timer = setTimeout(() => process.kill(), 30_000);

try {
            return await process.exitCode;
        } finally {
            clearTimeout(timer);
        }
    }
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
    async runWithTimeout() {
        if (!this.ctx.container.running) {
            await this.start();
        }

const process = await this.ctx.container.exec(["sleep", "120"]);
        const timer = setTimeout(() => process.kill(), 30_000);

try {
            return await process.exitCode;
        } finally {
            clearTimeout(timer);
        }
    }
}

Calling kill() without an argument queues a SIGTERM, signal 15. You can pass another signal when the process requires it. A process can handle or ignore a signal, so this is not a hard execution deadline. Observe completion through exitCode, and do not infer a specific exit code from a signal.

Stop the processes a command starts

kill() signals only the process that exec() started. Processes that this process starts, such as the commands in a bash -c script, keep running after exitCode resolves. output() waits until they exit.

To stop a command together with the processes it starts, run it under GNU coreutils timeout. The image must include timeout.

const process = await this.ctx.container.exec([
    "timeout",
    "--kill-after=5",
    "30",
    "bash",
    "-c",
    "npm test",
]);
const output = await process.output();

// 124 when the command times out, or 137 if timeout also sends SIGKILL.
console.log(output.exitCode);
const process = await this.ctx.container.exec([
    "timeout",
    "--kill-after=5",
    "30",
    "bash",
    "-c",
    "npm test",
]);
const output = await process.output();

// 124 when the command times out, or 137 if timeout also sends SIGKILL.
console.log(output.exitCode);

After 30 seconds, timeout sends SIGTERM to its process group. The group includes every process the command starts, unless a process moves to its own group, for example with setsid. With --kill-after=5, timeout sends SIGKILL to the group five seconds later if the command is still running.

Coordinate operations

Place exec() calls in the Durable Object that controls the Container. The Durable Object can coordinate process state and Container lifecycle.

One application RPC method can perform multiple exec() operations. Each command remains a separate exec operation, but the caller makes one Durable Object RPC call. This reduces caller-to-Durable Object round trips while keeping lifecycle decisions together.

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
    async runDiagnostics() {
        if (!this.ctx.container.running) {
            await this.start();
        }

const commands = [
            ["uname", "-a"],
            ["node", "--version"],
        ];
        const decoder = new TextDecoder();
        const results = [];

for (const command of commands) {
            const process = await this.ctx.container.exec(command);
            const output = await process.output();
            results.push({
                command,
                exitCode: output.exitCode,
                stdout: decoder.decode(output.stdout),
                stderr: decoder.decode(output.stderr),
            });
        }

return results;
    }
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
    async runDiagnostics() {
        if (!this.ctx.container.running) {
            await this.start();
        }

const commands = [
            ["uname", "-a"],
            ["node", "--version"],
        ];
        const decoder = new TextDecoder();
        const results = [];

for (const command of commands) {
            const process = await this.ctx.container.exec(command);
            const output = await process.output();
            results.push({
                command,
                exitCode: output.exitCode,
                stdout: decoder.decode(output.stdout),
                stderr: decoder.decode(output.stderr),
            });
        }

return results;
    }
}

For all fields and return types, refer to the exec() API contract.

Was this helpful?

YesNo

On this page

Execute commands·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/guides/execute-commands/#page","headline":"Execute commands","description":"Run additional processes inside an active Container.","url":"https://developers.cloudflare.com/containers/guides/execute-commands/","inLanguage":"en","image":"https://developers.cloudflare.com/containers/guides/execute-commands/og.png?v=1357ed940a06bbcf","dateModified":"2026-09-30","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Configure and update Container images with the durable_object and default scheduling policies. title: Image Management image: https://developers.cloudflare.com/containers/guides/image-management/og.png?v=3fd9a8c2d3fb3253


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt
Use this file to discover all available pages before exploring further.