# How to run Elixir code in a sandbox Install Elixir and Erlang in a Linux microVM, from apt or Elixir's own install script, and run the script there with `elixir`. **On Runtime Elixir installs with one command and each run gets a whole virtual machine, not just a BEAM process.** Each sandbox is a Firecracker microVM running Ubuntu 24.04.5 with passwordless `sudo`, so `apt-get install elixir` or the script from elixir-lang.org works unchanged. A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour while it waits and $0.08 an hour with both cores busy, because Runtime bills the CPU the code uses ([pricing](/docs/pricing)). ## Which Elixir to install Elixir is not in the default image. There are two routes, both read on 25 September 2026: | Route | Elixir | Erlang/OTP | Command | | ----------------------------- | ------ | ---------- | -------------------------------------- | | Ubuntu 24.04 (noble) packages | 1.14.0 | 25.3.2.8 | `sudo apt-get install -y elixir` | | elixir-lang.org `install.sh` | 1.20.4 | 28.4 | `sh install.sh elixir@1.20.4 otp@28.4` | The apt route is one line and pulls in the Erlang runtime it needs (`erlang-base` and friends). The install script fetches the current release and installs it under `$HOME/.elixir-install/installs`, which in a sandbox is `/workspace/.elixir-install/installs` ([install Elixir](https://elixir-lang.org/install/)). ## Run a script `.exs` files run directly with `elixir`, with no project and no compile step you have to manage. ```ts check import { Sandbox } from "withruntime"; const script = ` words = ~w(the quick brown fox jumps over the lazy dog) words |> Enum.frequencies_by(&String.length/1) |> Enum.sort() |> IO.inspect() `; await using sbx = await Sandbox.create({ timeoutSeconds: 600, onLeaseEnd: "stop" }); await sbx.exec("sudo apt-get update -q && sudo apt-get install -y -q elixir", { check: true, timeoutMs: 600_000, }); await sbx.network.set({ internet: false }); await sbx.files.write("/workspace/count.exs", script); const run = await sbx.exec(["elixir", "count.exs"], { timeoutMs: 30_000 }); console.log(run.exitCode, run.timedOut, run.stdout); ``` ```python check from withruntime import Sandbox script = """ words = ~w(the quick brown fox jumps over the lazy dog) words |> Enum.frequencies_by(&String.length/1) |> Enum.sort() |> IO.inspect() """ with Sandbox.create(timeout_seconds=600, on_lease_end="stop") as sbx: sbx.exec("sudo apt-get update -q && sudo apt-get install -y -q elixir", check=True, timeout_ms=600_000) sbx.network.set(internet=False) sbx.files.write("/workspace/count.exs", script) run = sbx.exec(["elixir", "count.exs"], timeout_ms=30_000) print(run.exit_code, run.timed_out, run.stdout) ``` A script that raises exits non-zero with the error and stack trace on stderr, which is what a model needs to correct its code. ## Why a sandbox and not a BEAM process Elixir code can call `System.cmd/2`, `:os.cmd/1`, `File.rm_rf!/1` or open a socket. Running a stranger's module inside your own node gives it all of your node's rights. In a sandbox the whole Erlang VM is inside a microVM with its own kernel, and the network, CPU, memory and cost limits are enforced on the host, where root in the sandbox cannot change them ([security](/docs/security)). ## Run a Mix project's tests Mix needs Hex to fetch dependencies. Fetch them with the network on, then cut the network before the tests run: ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ image: "elixir", timeoutSeconds: 1800, onLeaseEnd: "stop", }); await sbx.files.upload("./my_app", "/workspace/my_app"); const opts = { cwd: "/workspace/my_app", env: { MIX_ENV: "test" }, timeoutMs: 900_000 }; await sbx.exec("mix local.hex --force && mix deps.get && mix compile", { ...opts, check: true }); await sbx.network.set({ internet: false }); const tests = await sbx.exec(["mix", "test"], { ...opts, onStdout: (t) => process.stdout.write(t), }); process.exitCode = tests.exitCode ?? 1; ``` This uses the `elixir` image built below. Output streams while the tests run. ## Use the newest Elixir Elixir's install script puts OTP and Elixir side by side; add both `bin` folders to `PATH` for the command: ```python check from withruntime import Sandbox home = "/workspace/.elixir-install/installs" path = f"{home}/otp/28.4/bin:{home}/elixir/1.20.4-otp-28/bin:/usr/local/bin:/usr/bin:/bin" with Sandbox.create(timeout_seconds=900, on_lease_end="stop") as sbx: sbx.exec("curl -fsSO https://elixir-lang.org/install.sh && sh install.sh elixir@1.20.4 otp@28.4", check=True, timeout_ms=600_000) print(sbx.exec(["elixir", "--version"], env={"PATH": path}).stdout) ``` ## Start every sandbox with Elixir A recipe installs the Ubuntu packages once; `erlang-dev` adds the headers that dependencies with native code compile against. Building is free, and a stored image is charged on its size ([custom images](/docs/images)). ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); await runtime.images.build({ name: "elixir", recipe: { apt: ["elixir", "erlang-dev"] } }); await using sbx = await runtime.sandboxes.create({ image: "elixir" }); console.log((await sbx.exec(["elixir", "--version"])).stdout); ``` ```bash no-run runtime image build --apt elixir,erlang-dev --name elixir ``` To keep a node running between requests, start it with `sbx.spawn`, which returns at once and outlives your connection, and [pause](/how-to/pause-and-resume-a-sandbox) the sandbox when it is idle: a pause keeps memory and processes, and compute billing stops. New accounts get 50 free sandbox hours, no card: ```bash no-run npx withruntime login ``` See also [running untrusted LLM code](/use-cases/run-untrusted-llm-code), [grading student code](/use-cases/grade-student-code) and [per-user dev environments](/use-cases/per-user-dev-environments). ## Sources Checked 25 September 2026. - [Install Elixir](https://elixir-lang.org/install/): `install.sh`, Elixir 1.20.4 and OTP 28.4 - Ubuntu 24.04 (noble) packages: [elixir](https://packages.ubuntu.com/noble/elixir), [erlang-nox](https://packages.ubuntu.com/noble/erlang-nox), [erlang-dev](https://packages.ubuntu.com/noble/erlang-dev) Facts on this page were checked on 25 September 2026.