Skip to content

Run a Linux command

Last updated View as MarkdownAgent setup

You will POST uname -a to a Worker and read stdout that contains Linux.

Prerequisites

  1. Sign up for a Cloudflare account ↗︎.
  2. Install Node.js ↗︎.

Node.js version manager

Use a Node version manager like Volta ↗︎ or nvm ↗︎ to avoid permission issues and change Node.js versions. Wrangler, discussed later in this guide, requires a Node version of 16.17.0 or later.

Run a command in Linux

  1. Create a Worker project:

    npm create cloudflare@latest -- sandbox-linux --category=hello-world --type=hello-world --lang=ts --no-deploy --no-git --no-agents
  2. Change into the project directory:

    cd sandbox-linux
  3. Replace wrangler.jsonc so a Durable Object can start a container:

    {
    	"$schema": "node_modules/wrangler/config-schema.json",
    	"name": "sandbox-linux",
    	"main": "src/index.ts",
    	// Set this to today's date
    	"compatibility_date": "2026-10-05",
    	"observability": {
    		"enabled": true,
    	},
    	"upload_source_maps": true,
    	"containers": [
    		{
    			"class_name": "MyContainer",
    			"scheduling_policy": "durable_object",
    		},
    	],
    	"durable_objects": {
    		"bindings": [
    			{
    				"class_name": "MyContainer",
    				"name": "MY_CONTAINER",
    			},
    		],
    	},
    	"exports": {
    		"MyContainer": {
    			"type": "durable-object",
    			"storage": "sqlite",
    		},
    	},
    }
    "$schema" = "node_modules/wrangler/config-schema.json"
    name = "sandbox-linux"
    main = "src/index.ts"
    # Set this to today's date
    compatibility_date = "2026-10-05"
    upload_source_maps = true
    
    [observability]
    enabled = true
    
    [[containers]]
    class_name = "MyContainer"
    scheduling_policy = "durable_object"
    
    [[durable_objects.bindings]]
    class_name = "MyContainer"
    name = "MY_CONTAINER"
    
    [exports.MyContainer]
    type = "durable-object"
    storage = "sqlite"
  4. Replace src/index.ts. The Worker reads argv from the JSON body and runs it in Linux:

    src/index.jsjs
    import { DurableObject } from "cloudflare:workers";
    
    export class MyContainer extends DurableObject {
    	async exec(argv) {
    		const container = this.ctx.container;
    		if (!container) {
    			throw new Error("The container binding is not configured");
    		}
    
    		if (!container.running) {
    			container.start({
    				// Debian Trixie with Node.js 24
    				image: "cloudflare/debian-trixie",
    				// Keep the instance running so it can accept commands
    				entrypoint: ["sleep", "infinity"],
    				// Block commands in the sandbox from reaching the Internet
    				enableInternet: false,
    			});
    		}
    
    		const process = await container.exec(argv);
    		const output = await process.output();
    		return {
    			stdout: new TextDecoder().decode(output.stdout),
    			exitCode: output.exitCode,
    		};
    	}
    }
    
    export default {
    	async fetch(request, env) {
    		const { argv } = await request.json();
    		const sandbox = env.MY_CONTAINER.getByName("sandbox");
    		return Response.json(await sandbox.exec(argv));
    	},
    };
    src/index.tsts
    import { DurableObject } from "cloudflare:workers";
    
    export class MyContainer extends DurableObject<Env> {
    	async exec(argv: string[]) {
    		const container = this.ctx.container;
    		if (!container) {
    			throw new Error("The container binding is not configured");
    		}
    
    		if (!container.running) {
    			container.start({
    				// Debian Trixie with Node.js 24
    				image: "cloudflare/debian-trixie",
    				// Keep the instance running so it can accept commands
    				entrypoint: ["sleep", "infinity"],
    				// Block commands in the sandbox from reaching the Internet
    				enableInternet: false,
    			});
    		}
    
    		const process = await container.exec(argv);
    		const output = await process.output();
    		return {
    			stdout: new TextDecoder().decode(output.stdout),
    			exitCode: output.exitCode,
    		};
    	}
    }
    
    export default {
    	async fetch(request: Request, env: Env): Promise<Response> {
    		const { argv } = (await request.json()) as { argv: string[] };
    		const sandbox = env.MY_CONTAINER.getByName("sandbox");
    		return Response.json(await sandbox.exec(argv));
    	},
    };
  5. Generate types for the binding. Wrangler reads the MyContainer class from src/index.ts to type env.MY_CONTAINER:

    npx wrangler types
  6. Run wrangler dev:

    npx wrangler dev

    wrangler dev runs the instance in Docker ↗︎ on your machine, so Docker must be running. Running cloudflare/debian-trixie locally needs Wrangler 4.141.0 or later.

  7. POST a command to the URL Wrangler prints. The default is http://localhost:8787:

    curl http://localhost:8787 --request POST --json '{"argv":["uname","-a"]}'

The JSON body includes "exitCode":0. stdout contains Linux. The Worker started a Linux VM and ran the command you sent.

Next steps

Was this helpful?