# How to run Haskell code in a sandbox Install GHC in a Linux microVM, from apt or GHCup, then run the file with `runghc` or compile it with `ghc` and run the binary. **On Runtime GHC comes from Ubuntu's archive in one command, and a compile that takes a while is billed only for the CPU it uses.** Each sandbox is a Firecracker microVM running Ubuntu 24.04.5, where `ghc`, `cabal-install` and `haskell-stack` are apt packages. Runtime charges $0.025 per vCPU-hour of measured CPU and $0.0075 per GiB-hour of memory, so a 2 vCPU, 4 GiB sandbox costs $0.08 an hour at full load and $0.03125 while it waits ([pricing](/docs/pricing)). ## What to install Haskell is not in the default image. These are the options, read on 25 September 2026: | Tool | Ubuntu 24.04 (noble) package | Version on noble | Use it for | | ------------------ | ---------------------------- | ---------------- | -------------------------------- | | GHC | `ghc` | 9.4.7 | `ghc` and `runghc` | | Cabal | `cabal-install` | 3.8.1.0 | Packages from Hackage | | Stack | `haskell-stack` | 2.9.3.1 | Stackage snapshots | | GHCup (not in apt) | `get-ghcup.haskell.org` | its own | Newer GHC and switching versions | GHCup installs into `~/.ghcup/bin`, which in a sandbox is `/workspace/.ghcup/bin` ([GHCup](https://www.haskell.org/ghcup/install/)). Its script runs without prompts when `BOOTSTRAP_HASKELL_NONINTERACTIVE=1` is set. ## Run a Haskell file `runghc` interprets a file with no build step. It suits short programs and exercises; compile anything that loops heavily. ```ts check import { Sandbox } from "withruntime"; const program = `import Data.List (sortOn) import qualified Data.Map.Strict as M main :: IO () main = do let ws = words "the quick brown fox jumps over the lazy dog the end" counts = M.toList (M.fromListWith (+) [(w, 1 :: Int) | w <- ws]) mapM_ print (take 3 (sortOn (negate . snd) counts)) `; await using sbx = await Sandbox.create({ timeoutSeconds: 900, onLeaseEnd: "stop" }); await sbx.exec("sudo apt-get update -q && sudo apt-get install -y -q ghc", { check: true, timeoutMs: 600_000, }); await sbx.network.set({ internet: false }); await sbx.files.write("/workspace/Main.hs", program); const run = await sbx.exec(["runghc", "Main.hs"], { timeoutMs: 120_000 }); console.log(run.exitCode, run.timedOut, run.stdout); ``` ```python check from withruntime import Sandbox program = """import Data.List (sortOn) import qualified Data.Map.Strict as M main :: IO () main = do let ws = words "the quick brown fox jumps over the lazy dog the end" counts = M.toList (M.fromListWith (+) [(w, 1 :: Int) | w <- ws]) mapM_ print (take 3 (sortOn (negate . snd) counts)) """ with Sandbox.create(timeout_seconds=900, on_lease_end="stop") as sbx: sbx.exec("sudo apt-get update -q && sudo apt-get install -y -q ghc", check=True, timeout_ms=600_000) sbx.network.set(internet=False) sbx.files.write("/workspace/Main.hs", program) run = sbx.exec(["runghc", "Main.hs"], timeout_ms=120_000) print(run.exit_code, run.timed_out, run.stdout) ``` `Data.Map` comes from the `containers` library, which ships with GHC, so nothing is fetched from Hackage. ## Compile, then run Compile with optimisation, then run the binary under its own time limit. A type error comes back from the compile step on stderr, with the file, line and column, and the run never starts. ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ image: "haskell", network: { internet: false } }); await sbx.files.write( "/workspace/Sum.hs", "main :: IO ()\nmain = print (sum [x * x | x <- [1 .. 1000000 :: Int], even x])\n", ); const build = await sbx.exec(["ghc", "-O2", "-rtsopts", "-o", "sum", "Sum.hs"], { timeoutMs: 300_000, }); if (build.exitCode !== 0) throw new Error(build.stderr); const run = await sbx.exec(["./sum", "+RTS", "-M512m", "-RTS"], { timeoutMs: 30_000 }); console.log(run.stdout); ``` `-rtsopts` lets the binary take runtime options, and `+RTS -M512m -RTS` then caps its heap at 512 MB from inside GHC's runtime, on top of the sandbox's own memory size. This uses the `haskell` image built below. ## Build a Cabal project Cabal downloads the package index and dependencies from Hackage. Let it do that with the network on, then run the tests offline: ```python check import sys from withruntime import Sandbox with Sandbox.create(image="haskell", disk_mib=8192, timeout_seconds=3600, on_lease_end="stop") as sbx: sbx.files.upload("./parser", "/workspace/parser") opts = {"cwd": "/workspace/parser", "timeout_ms": 1_800_000, "on_stdout": sys.stdout.write} sbx.exec("cabal update && cabal build --only-dependencies --enable-tests", check=True, **opts) sbx.network.set(internet=False) tests = sbx.exec(["cabal", "test"], **opts) sys.exit(tests.exit_code or 0) ``` Dependencies compile from source, which is the slow part of a first Haskell build. Do it once and [snapshot](/glossary/sandbox-snapshot) the sandbox, and later runs start with the dependencies already built. ## Start every sandbox with GHC ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); await runtime.images.build({ name: "haskell", recipe: { apt: ["ghc", "cabal-install"] } }); await using sbx = await runtime.sandboxes.create({ image: "haskell" }); console.log((await sbx.exec(["ghc", "--version"])).stdout); ``` ```bash no-run runtime image build --apt ghc,cabal-install --name haskell ``` Building an image is free, and the free trial stores your first three images free ([custom images](/docs/images)). ## Why run Haskell in a sandbox at all? Types do not stop side effects. A Haskell program in `IO` can delete files, start processes and open sockets like any other, and Template Haskell runs code at compile time. A sandbox puts the compiler and the program in a microVM with its own kernel, with network, CPU, memory and cost enforced on the host ([security](/docs/security)). New accounts get 50 free sandbox hours, no card: ```bash no-run npx withruntime login ``` See also [grading student code](/use-cases/grade-student-code), [running untrusted LLM code](/use-cases/run-untrusted-llm-code) and [how to run Elixir code in a sandbox](/languages/elixir). ## Sources Checked 25 September 2026. - Ubuntu 24.04 (noble) packages: [ghc](https://packages.ubuntu.com/noble/ghc), [cabal-install](https://packages.ubuntu.com/noble/cabal-install), [haskell-stack](https://packages.ubuntu.com/noble/haskell-stack) - [GHCup installation](https://www.haskell.org/ghcup/install/) and its [bootstrap script](https://get-ghcup.haskell.org) (`BOOTSTRAP_HASKELL_NONINTERACTIVE`) Facts on this page were checked on 25 September 2026.