# How to convert Markdown to PDF or Word with Pandoc in a sandbox Install pandoc and a PDF engine in a microVM, run pandoc in.md -o out.pdf or out.docx with --sandbox, and download the file. **On Runtime, model-written Markdown becomes a PDF or a Word file on a machine that can read nothing but that Markdown.** Each sandbox is a Firecracker microVM with its own kernel and disk, and pandoc's own `--sandbox` flag narrows it further to the files named on the command line. Runtime bills the CPU a render uses, not the CPU it holds: a 2 vCPU, 4 GiB sandbox costs $0.03125 an hour between documents and $0.08 with both CPUs busy ([pricing](/docs/pricing)). ## Which pandoc | Source | Version on 25 September 2026 | Install | | --------------------------------------- | ---------------------------- | ---------------------------------------------- | | Ubuntu 24.04 package `pandoc` | 3.1.3 | `sudo apt-get install -y pandoc` | | pandoc's GitHub release, 29 August 2026 | 3.11 | `pandoc-3.11-1-amd64.deb`, then `sudo dpkg -i` | pandoc's install page recommends the release `.deb` for Linux. It downloads from GitHub over HTTPS, which the free trial reaches. ## Choose the PDF engine pandoc makes a PDF through another program. Its manual names `pdflatex` as the default and lists `lualatex`, `xelatex`, `latexmk`, `tectonic`, `wkhtmltopdf`, `weasyprint`, `pagedjs-cli`, `prince`, `context`, `groff`, `pdfroff` and `typst`. The ones Ubuntu 24.04 packages: | Engine | Ubuntu package(s) | Good for | | ------------- | ------------------------------------------------------------------- | --------------------------------------- | | `pdflatex` | `texlive-latex-recommended`, `texlive-fonts-recommended`, `lmodern` | Papers, maths, the classic LaTeX look | | `xelatex` | `texlive-xetex` | System fonts and non-Latin scripts | | `weasyprint` | `weasyprint` (61.1) | Styling a PDF with CSS, a small install | | `wkhtmltopdf` | `wkhtmltopdf` (0.12.6) | HTML-first layouts | TeX Live packages are 2023.20240207 in noble. Typst is not in Ubuntu's archive for 24.04. ## Markdown to PDF and DOCX ```ts check import { writeFile } from "node:fs/promises"; import { Sandbox } from "withruntime"; const markdown = "# Quarterly report\n\nRevenue rose **12%**.\n\n| Region | Sales |\n|---|---|\n| West | 120 |\n"; await using sbx = await Sandbox.create({ diskMiB: 6144, timeoutSeconds: 900 }); await sbx.exec( "curl -fsSLO https://github.com/jgm/pandoc/releases/download/3.11/pandoc-3.11-1-amd64.deb" + " && sudo dpkg -i pandoc-3.11-1-amd64.deb" + " && sudo apt-get update -qq && sudo apt-get install -y -qq weasyprint", { check: true, timeoutMs: 600_000 }, ); await sbx.files.write("/workspace/report.md", markdown); await sbx.files.upload("./report.css", "/workspace/report.css"); await sbx.exec( "pandoc --sandbox report.md -s --css report.css --pdf-engine=weasyprint -o report.pdf", { check: true, timeoutMs: 120_000 }, ); await sbx.exec("pandoc --sandbox report.md -o report.docx", { check: true }); await writeFile("report.pdf", await sbx.files.read("/workspace/report.pdf")); await writeFile("report.docx", await sbx.files.read("/workspace/report.docx")); ``` ```python check from withruntime import Sandbox markdown = "# Quarterly report\n\nRevenue rose **12%**.\n" with Sandbox.create(disk_mib=6144, timeout_seconds=900) as sbx: sbx.exec("curl -fsSLO https://github.com/jgm/pandoc/releases/download/3.11/pandoc-3.11-1-amd64.deb" " && sudo dpkg -i pandoc-3.11-1-amd64.deb" " && sudo apt-get update -qq && sudo apt-get install -y -qq weasyprint", check=True, timeout_ms=600_000) sbx.files.write("/workspace/report.md", markdown) sbx.files.upload("report.css", "/workspace/report.css") sbx.exec("pandoc --sandbox report.md -s --css report.css --pdf-engine=weasyprint -o report.pdf", check=True, timeout_ms=120_000) sbx.exec("pandoc --sandbox report.md -o report.docx", check=True) sbx.files.download("/workspace/report.pdf", "report.pdf") sbx.files.download("/workspace/report.docx", "report.docx") ``` pandoc picks the output format from the file extension, and `.docx` needs no extra program. ## Why `--sandbox` Markdown can pull in other files, through an image path or an include. When the text comes from a model or a user, that is a way to read files it should not. pandoc's manual describes `--sandbox` as limiting input and output in readers and writers "to reading the files specified on the command line", which it offers "as security against disclosure of files through the use of include directives". In a Runtime sandbox the two layers stack: pandoc reads only the files you named, and the machine it runs on holds only the files you uploaded. Turn the internet off after the install too, so nothing a document references can be fetched ([turn off sandbox internet](/how-to/turn-off-sandbox-internet)). ## House style for Word output `--reference-doc` takes a `.docx` whose styles, margins, page size, header and footer pandoc copies into the output, per its manual. Its contents are ignored. Start from one pandoc wrote, edit the styles in Word, and upload it: ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ image: "pandoc" }); await sbx.files.upload("./house-style.docx", "/workspace/house-style.docx"); await sbx.files.upload("./memo.md", "/workspace/memo.md"); await sbx.exec("pandoc --sandbox memo.md --reference-doc=house-style.docx -o memo.docx", { check: true, }); await sbx.files.download("/workspace/memo.docx", "./memo.docx"); ``` To make the starting file: `pandoc -o custom-reference.docx --print-default-data-file reference.docx`. ## Start every sandbox with pandoc installed The `pandoc` image the last sample uses, as a [custom image](/docs/images). Recipe commands run as root, so they need no `sudo`: ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); await runtime.images.build({ name: "pandoc", recipe: { apt: ["weasyprint"], commands: [ "curl -fsSLO https://github.com/jgm/pandoc/releases/download/3.11/pandoc-3.11-1-amd64.deb", "dpkg -i pandoc-3.11-1-amd64.deb && rm pandoc-3.11-1-amd64.deb", ], }, }); ``` ```python check from withruntime import Runtime Runtime().images.build(name="pandoc", recipe={ "apt": ["weasyprint"], "commands": [ "curl -fsSLO https://github.com/jgm/pandoc/releases/download/3.11/pandoc-3.11-1-amd64.deb", "dpkg -i pandoc-3.11-1-amd64.deb && rm pandoc-3.11-1-amd64.deb", ], }) ``` For LaTeX output, add the TeX Live packages from the engine table to `apt` and raise the build machine's scratch disk with `build: { diskMiB: 8192 }`; it takes 1 to 32 GiB. Building is free, and the trial stores three images free. ## Limits that apply - **Disk.** The default 4 GiB disk had about 2.5 GiB free on 24 September 2026. WeasyPrint fits; TeX Live wants `diskMiB: 8192`, within the trial's 10 GiB. - **Output in the result.** `pandoc -t plain` or `-t html` to stdout is kept up to 64 KiB in the command result; write longer output to a file. - **Fonts.** A PDF uses the fonts installed in the sandbox. Add the font packages your CSS or LaTeX template names. To go from Word, Excel or PowerPoint to PDF instead, see [LibreOffice in a sandbox](/integrations/libreoffice). For a chat product that returns generated files, see [a code interpreter for chatbots](/use-cases/code-interpreter-for-chatbots). ## Sources - pandoc manual, https://pandoc.org/MANUAL.html, read 25 September 2026 - Installing pandoc, https://pandoc.org/installing.html, read 25 September 2026 - pandoc 3.11 release assets, https://github.com/jgm/pandoc/releases/tag/3.11, read 25 September 2026 - Ubuntu 24.04 package pages (pandoc, weasyprint, wkhtmltopdf, texlive-*), https://packages.ubuntu.com/noble/, read 25 September 2026 Facts on this page were checked on 25 September 2026.