# 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, Markdown written by a model 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. Pandoc's own `--sandbox` flag narrows access further, to the files named on the command line. Runtime bills the CPU a render uses, not the CPU the sandbox holds. A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour between documents, and $0.08 an hour with both CPUs busy ([pricing](/docs/pricing)). ## Which pandoc | Source | Version on 25 October 2026 | Install | | --------------------------------------- | -------------------------- | ------------------------------------------------ | | Ubuntu 24.04 package `pandoc` | 3.1.3 | `sudo apt-get install -y pandoc` | | pandoc's GitHub release, 8 October 2026 | 3.12.1 | `pandoc-3.12.1-1-amd64.deb`, then `sudo dpkg -i` | pandoc's install page offers the release `.deb` for the latest version on Linux. It downloads from GitHub over HTTPS, which a sandbox without credit can reach. ## Choose the PDF engine pandoc makes a PDF through another program. Its manual names `pdflatex` as the default. It also lists `lualatex`, `xelatex`, `latexmk`, `tectonic`, `wkhtmltopdf`, `weasyprint`, `pagedjs-cli`, `prince`, `context`, `groff`, `pdfroff` and `typst`. The table below shows the ones that 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 | The TeX Live packages in noble are 2023.20240207. Ubuntu's archive for 24.04 does not include Typst. ## 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.12.1/pandoc-3.12.1-1-amd64.deb" + " && sudo dpkg -i pandoc-3.12.1-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.12.1/pandoc-3.12.1-1-amd64.deb" " && sudo dpkg -i pandoc-3.12.1-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. The `.docx` format 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 lets it read files it should not. The pandoc manual describes `--sandbox` as limiting input and output in readers and writers "to reading the files specified on the command line". It offers this "security against, for example, disclosure of files through the use of include directives". In a Runtime sandbox, the two layers work together. Pandoc reads only the files you named, and the machine holds only the files you uploaded. Turn the internet off after the install as well. Then 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` file. According to the pandoc manual, pandoc copies its styles, margins, page size, header and footer into the output. Its contents are ignored. Start from a file that 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, run `pandoc -o custom-reference.docx --print-default-data-file reference.docx`. ## Start every sandbox with pandoc installed This is the `pandoc` image that 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.12.1/pandoc-3.12.1-1-amd64.deb", "dpkg -i pandoc-3.12.1-1-amd64.deb && rm pandoc-3.12.1-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.12.1/pandoc-3.12.1-1-amd64.deb", "dpkg -i pandoc-3.12.1-1-amd64.deb && rm pandoc-3.12.1-1-amd64.deb", ], }) ``` For LaTeX output, add the TeX Live packages from the engine table to `apt`. Also raise the build machine's scratch disk with `build: { diskMiB: 8192 }`. The scratch disk takes 4 GiB to 32 GiB. Building is free. An account without credit can store 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 needs `diskMiB: 8192`, which is within the 10 GiB allowed without credit. - **Output in the result.** Output from `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 that your CSS or LaTeX template names. To convert Word, Excel or PowerPoint files to PDF, 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 October 2026 - Installing pandoc, https://pandoc.org/installing.html, read 25 October 2026 - pandoc 3.12.1 release assets, https://github.com/jgm/pandoc/releases/tag/3.12.1, read 25 October 2026 - Ubuntu 24.04 packages (pandoc, weasyprint, wkhtmltopdf, texlive-*), from the noble package index at http://archive.ubuntu.com/ubuntu/dists/noble/universe/binary-amd64/, read 25 October 2026 Facts on this page were checked on 25 October 2026.