Skip to content
Runtime

How to run C# (.NET) code in a sandbox

Install the .NET SDK in a Linux microVM. Then build and run the C# code with dotnet, under a time limit and with no network access while it runs.

On Runtime, the .NET SDK comes from Ubuntu's own archive, and one C# file runs with no project. Each sandbox is a Firecracker microVM that runs Ubuntu 24.04.5. Both dotnet-sdk-10.0 and dotnet-sdk-8.0 are apt packages in that image. The file-based apps in .NET 10 run a single .cs file with dotnet run app.cs. Runtime bills the CPU a build uses at $0.025 per vCPU-hour. A sandbox that waits for its next job costs a twentieth of a vCPU (pricing).

Which .NET to install

.NET is not in the default image. Ubuntu 24.04 (noble) includes both SDKs. The versions below were read on Ubuntu's archive on 26 October 2026:

Package Version on noble Runs a lone .cs file?
dotnet-sdk-10.0 10.0.113 (noble-updates) Yes: dotnet run app.cs
dotnet-sdk-8.0 8.0.132 No: needs a .csproj

According to Microsoft's documentation, file-based apps work with the .NET 10 SDK and later (file-based apps).

Two environment variables keep the output clean for a program to read. DOTNET_CLI_TELEMETRY_OPTOUT=1 turns off the SDK's usage telemetry. DOTNET_NOLOGO=true hides the first-run welcome text (.NET CLI telemetry).

Run one C# file

Install the SDK. Build the file while the network is on, because a build restores packages from NuGet. Then turn off internet access and run the built program.

TypeScriptimport { Sandbox } from "withruntime";const program = `using System;using System.Linq;var primes = Enumerable.Range(2, 50).Where(n => Enumerable.Range(2, n - 2).All(d => n % d != 0));Console.WriteLine(string.Join(", ", primes));`;const env = { DOTNET_CLI_TELEMETRY_OPTOUT: "1", DOTNET_NOLOGO: "true" };await using sbx = await Sandbox.create({ diskMiB: 8192, timeoutSeconds: 900, onLeaseEnd: "stop" });await sbx.exec("sudo apt-get update -q && sudo apt-get install -y -q dotnet-sdk-10.0", {  check: true,  timeoutMs: 600_000,});await sbx.files.write("/workspace/app/primes.cs", program);const build = await sbx.exec(["dotnet", "build", "primes.cs"], {  cwd: "/workspace/app",  env,  timeoutMs: 300_000,});if (build.exitCode !== 0) throw new Error(build.stdout + build.stderr);await sbx.network.set({ internet: false });const run = await sbx.exec(["dotnet", "run", "primes.cs", "--no-build"], {  cwd: "/workspace/app",  env,  timeoutMs: 30_000,});console.log(run.exitCode, run.stdout);
Pythonfrom withruntime import Sandboxprogram = """using System;using System.Linq;var primes = Enumerable.Range(2, 50).Where(n => Enumerable.Range(2, n - 2).All(d => n % d != 0));Console.WriteLine(string.Join(", ", primes));"""env = {"DOTNET_CLI_TELEMETRY_OPTOUT": "1", "DOTNET_NOLOGO": "true"}with Sandbox.create(disk_mib=8192, timeout_seconds=900, on_lease_end="stop") as sbx:    sbx.exec("sudo apt-get update -q && sudo apt-get install -y -q dotnet-sdk-10.0", check=True, timeout_ms=600_000)    sbx.files.write("/workspace/app/primes.cs", program)    build = sbx.exec(["dotnet", "build", "primes.cs"], cwd="/workspace/app", env=env, timeout_ms=300_000)    if build.exit_code != 0:        raise SystemExit(build.stdout + build.stderr)    sbx.network.set(internet=False)    run = sbx.exec(["dotnet", "run", "primes.cs", "--no-build"], cwd="/workspace/app", env=env, timeout_ms=30_000)    print(run.exit_code, run.stdout)
  • Disk: request 8 GiB. The default 4 GiB sandbox has about 2.5 GiB free (the sandbox environment). The SDK and its build output share that space.
  • Timeouts: an apt install and a first build can run longer than the 60-second default for a command. Give each one its own timeoutMs.
  • Compiler errors from dotnet build include the file and line. Return them to the model that wrote the code.
  • NuGet packages go at the top of the file as #:package Name@version. The build restores them while the network is still on.

Build and test a project

For a solution with .csproj files, upload the folder and run the usual commands. dotnet-sdk-8.0 also works, for projects that target .NET 8.

Pythonimport sysfrom withruntime import Sandboxenv = {"DOTNET_CLI_TELEMETRY_OPTOUT": "1", "DOTNET_NOLOGO": "true"}with Sandbox.create(disk_mib=8192, timeout_seconds=1800, on_lease_end="stop") as sbx:    sbx.exec("sudo apt-get update -q && sudo apt-get install -y -q dotnet-sdk-8.0", check=True, timeout_ms=600_000)    sbx.files.upload("./Service", "/workspace/Service")    sbx.exec(["dotnet", "restore"], cwd="/workspace/Service", env=env, check=True, timeout_ms=600_000)    sbx.network.set(internet=False)    tests = sbx.exec(["dotnet", "test", "--no-restore"], cwd="/workspace/Service", env=env,                     timeout_ms=900_000, on_stdout=sys.stdout.write)    sys.exit(tests.exit_code or 0)

Restoring first and then testing with --no-restore means the tests never use the network. Output streams back while the tests run.

Start every sandbox with .NET

Install the SDK once in a custom image. Set the two variables for every command:

TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({  name: "dotnet",  recipe: {    apt: ["dotnet-sdk-10.0"],    env: { DOTNET_CLI_TELEMETRY_OPTOUT: "1", DOTNET_NOLOGO: "true" },  },});await using sbx = await runtime.sandboxes.create({ image: "dotnet", diskMiB: 8192 });console.log((await sbx.exec(["dotnet", "--version"])).stdout);

Building an image is free, and without credit it counts toward the account's hours. A stored image is billed by its size.

Serve an ASP.NET Core app

Start the app with spawn, so it keeps running after the command returns. Share its port as a private HTTPS preview. spawn returns immediately. The process keeps running after your connection ends.

TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ image: "dotnet", diskMiB: 8192, timeoutSeconds: 1800 });await sbx.files.upload("./WebApi", "/workspace/WebApi");await sbx.spawn("cd WebApi && dotnet run --urls http://0.0.0.0:5000");await sbx.exec("npx wait-on http://localhost:5000", { check: true, timeoutMs: 300_000 });const preview = await sbx.previews.create(5000);console.log(preview.urlWithToken);

A new account gets 100 free hours of a 1 GB machine in its first 30 days. No payment card is needed:

Terminalnpx withruntime login

See also running untrusted LLM code, grading student code and how to run C++ code in a sandbox.

Sources

Checked 26 October 2026.

Facts on this page were checked on 26 October 2026.

Related.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card