Skip to content
Runtime

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).

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

TypeScriptimport { 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"));
Pythonfrom withruntime import Sandboxmarkdown = "# 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).

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:

TypeScriptimport { 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. Recipe commands run as root, so they need no sudo:

TypeScriptimport { 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",    ],  },});
Pythonfrom withruntime import RuntimeRuntime().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. For a chat product that returns generated files, see a code interpreter for chatbots.

Sources

Facts on this page were checked on 25 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