How to convert Word documents to PDF on a server with LibreOffice headless
Install LibreOffice in an isolated sandbox image, upload the file, run soffice --headless --convert-to pdf, and download the PDF.
On Runtime the converter image is built once for free, and each conversion pays only for the seconds it runs. A 2 vCPU, 4 GiB sandbox with both CPUs busy costs $0.08 an hour, and 5,000 thirty-second conversions come to $1.77 at the rates published on 25 September 2026, worked out below. The uploaded file is opened inside a Firecracker microVM with its own kernel and no network, so a hostile document reaches nothing of yours.
Why convert in a sandbox
Users upload DOCX, ODT and RTF files, and agents produce Markdown that has to become a Word file or a web page. The converters that do this well, LibreOffice and pandoc, are large programs that parse complex formats written by strangers. Running them on an application server puts that parsing next to your database credentials and your other customers' files.
A sandbox per job, or per batch, puts it somewhere else: a machine with no network card, no credentials and a lease that ends it.
Build the converter image
Ubuntu 24.04 packages both tools. libreoffice-writer-nogui is LibreOffice's
word processor without a graphical interface (version 24.2.7 in noble), and
pandoc is 3.1.3 there (Launchpad, read 25 September 2026):
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build( { name: "convert", recipe: { apt: ["libreoffice-writer-nogui", "pandoc"] } }, { onLog: (line) => console.log(line.text) },);Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build( name="convert", recipe={"apt": ["libreoffice-writer-nogui", "pandoc"]}, on_log=lambda line: print(line["text"]),)The build runs in its own Firecracker machine and costs nothing; the stored image is billed on its size at $0.08 per decimal GB per 30-day month, and a free trial keeps its first three images free (custom images).
Convert one document
--headless runs LibreOffice with no user interface, and --convert-to pdf
writes the result into --outdir (LibreOffice's help, read 25 September 2026).
TypeScriptimport { readFile, writeFile } from "node:fs/promises";import { Runtime } from "withruntime";const runtime = new Runtime();await using sbx = await runtime.sandboxes.create({ image: "convert", network: { internet: false }, timeoutSeconds: 600, onLeaseEnd: "stop",});await sbx.files.write("/workspace/in/contract.docx", await readFile("contract.docx"));const run = await sbx.exec( [ "soffice", "--headless", "--convert-to", "pdf", "--outdir", "/workspace/out", "/workspace/in/contract.docx", ], { timeoutMs: 180_000 },);if (run.exitCode !== 0 || run.timedOut) throw new Error(run.stderr);await writeFile("contract.pdf", await sbx.files.read("/workspace/out/contract.pdf"));Pythonfrom withruntime import Runtimeruntime = Runtime()with runtime.sandboxes.create( image="convert", network={"internet": False}, timeout_seconds=600, on_lease_end="stop",) as sbx: with open("contract.docx", "rb") as source: sbx.files.write("/workspace/in/contract.docx", source.read()) run = sbx.exec( ["soffice", "--headless", "--convert-to", "pdf", "--outdir", "/workspace/out", "/workspace/in/contract.docx"], timeout_ms=180_000, ) if run.exit_code != 0 or run.timed_out: raise RuntimeError(run.stderr) with open("contract.pdf", "wb") as target: target.write(sbx.files.read("/workspace/out/contract.pdf"))The command is an array, so a file name with spaces or quotes reaches LibreOffice as one argument and never passes through a shell. Treat the uploaded name as data: store it under a name you choose, as above.
Convert a folder at once
Starting LibreOffice once for many files is faster than once per file. Upload the folder in one call, convert every file with a glob, and bring the results back in one call:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ image: "convert", network: { internet: false } });await sbx.files.upload("./incoming", "/workspace/in");await sbx.exec("soffice --headless --convert-to pdf --outdir /workspace/out /workspace/in/*.docx", { timeoutMs: 1_800_000,});console.log(await sbx.files.glob("out/*.pdf"));await sbx.files.download("/workspace/out", "./converted");Directories travel as one compressed archive each way, and uploaded files keep their permissions.
Markdown from an agent to DOCX or HTML
pandoc converts between markup formats; -f and -t name them and -o names
the output file. Its PDF output goes through LaTeX by default, which needs a
LaTeX engine installed, so for PDF it is simpler to make DOCX with pandoc and
hand that to LibreOffice:
Terminalpandoc -f markdown -t docx -o report.docx report.mdsoffice --headless --convert-to pdf report.docxWhat a conversion service needs
| Need | How Runtime covers it |
|---|---|
| Isolation from hostile files | A Firecracker microVM with its own kernel per sandbox |
| A document that phones home | network: { internet: false }, enforced outside the guest |
| LibreOffice and pandoc ready at start | A custom image from a recipe, a Dockerfile or any container image |
| A conversion that hangs | timeoutMs on the command; the lease ends the sandbox |
| Big files | Chunked, SHA-256 checked uploads with no limit beyond the disk |
| Bursts of uploads | Creates wait for room rather than failing, for up to two minutes |
| Pinning a converter version | image: "convert@3" starts from exactly that build |
What it costs
Take 5,000 documents a month, each converted in its own 2 vCPU, 4 GiB sandbox that runs for 30 seconds with 15 CPU-seconds of work:
TextCPU: 5,000 × 15 s / 3,600 × $0.025 = $0.52Memory: 5,000 × 30 s / 3,600 × 4 GiB × $0.0075 = $1.25Total: $1.77Converting in batches of 50 per sandbox cuts the memory line further, since LibreOffice starts once per batch. Runtime bills $0.025 per vCPU-hour of measured CPU and $0.0075 per GiB-hour of memory, with no plan fee (pricing). The trial's 50 free hours need no card.
Sources
- LibreOffice command line parameters:
--headless,--convert-toand--outdir, read 25 September 2026. - Pandoc user's guide: formats,
-o, and PDF through LaTeX, read 25 September 2026. - Launchpad package pages for libreoffice-writer-nogui and pandoc in Ubuntu 24.04, read 25 September 2026.
Related: PDF processing, run untrusted LLM code safely, turn off a sandbox's internet, Docker vs a virtual machine.
Facts on this page were checked on 25 September 2026.