On Runtime, documents from strangers are opened on a machine that exists only for them. An uploaded file is untrusted input, and every sandbox is a Firecracker microVM with its own kernel and no route to your network, and you can cut off its internet access entirely. Conversion is short CPU work in bursts, and Runtime bills the CPU used: a 2 vCPU, 4 GiB sandbox costs $0.08 an hour at full load and $0.03125 while it waits for the next file (pricing). Ubuntu 24.04's updates carried LibreOffice 24.2.7 on 25 October 2026.
Pick the packages
Ubuntu builds each LibreOffice program in a variant without a graphical interface. Install only the ones for the formats you convert:
| Converting from | Package |
|---|---|
.docx, .doc, .odt, .rtf |
libreoffice-writer-nogui |
.xlsx, .xls, .ods, .csv |
libreoffice-calc-nogui |
.pptx, .ppt, .odp |
libreoffice-impress-nogui |
| All of them | libreoffice-nogui |
Fonts decide whether a converted page looks like the original. These Ubuntu packages are metric-compatible substitutes for the fonts that Office documents usually name. Lines therefore break where the author saw them:
| Package | Stands in for |
|---|---|
fonts-liberation |
Times New Roman, Arial, Courier |
fonts-crosextra-carlito |
Calibri |
fonts-crosextra-caladea |
Cambria |
Descriptions and versions are from Ubuntu's package archive, read 25 October 2026.
Convert a document
TypeScriptimport { writeFile } from "node:fs/promises";import { Sandbox } from "withruntime";const packages = "libreoffice-writer-nogui fonts-liberation fonts-crosextra-carlito fonts-crosextra-caladea";await using sbx = await Sandbox.create({ diskMiB: 8192, timeoutSeconds: 1800 });await sbx.exec(`sudo apt-get update -qq && sudo apt-get install -y -qq ${packages}`, { check: true, timeoutMs: 900_000,});await sbx.network.off(); // nothing the document triggers can reach the internetawait sbx.files.upload("./contract.docx", "/workspace/in/contract.docx");await sbx.exec( [ "soffice", "--headless", "--convert-to", "pdf", "--outdir", "/workspace/out", "/workspace/in/contract.docx", ], { check: true, timeoutMs: 300_000 },);await writeFile("contract.pdf", await sbx.files.read("/workspace/out/contract.pdf"));Pythonfrom withruntime import SandboxPACKAGES = ("libreoffice-writer-nogui fonts-liberation " "fonts-crosextra-carlito fonts-crosextra-caladea")with Sandbox.create(disk_mib=8192, timeout_seconds=1800) as sbx: sbx.exec(f"sudo apt-get update -qq && sudo apt-get install -y -qq {PACKAGES}", check=True, timeout_ms=900_000) sbx.network.off() # nothing the document triggers can reach the internet sbx.files.upload("contract.docx", "/workspace/in/contract.docx") sbx.exec(["soffice", "--headless", "--convert-to", "pdf", "--outdir", "/workspace/out", "/workspace/in/contract.docx"], check=True, timeout_ms=300_000) sbx.files.download("/workspace/out/contract.pdf", "contract.pdf")The output keeps the input's name with the new extension. Without --outdir,
LibreOffice writes to the working directory, which in a sandbox is
/workspace.
Other conversions from the same command
LibreOffice's command-line guide gives the format as
extension[:filter[:options]]:
| Command | Result |
|---|---|
soffice --headless --convert-to pdf *.docx --outdir out |
Every Word file in the folder to PDF |
soffice --headless --convert-to pdf:writer_pdf_Export in.doc |
PDF with the Writer export filter named |
soffice --headless --convert-to docx in.doc |
An old .doc to .docx |
soffice --headless --convert-to csv in.xlsx |
A spreadsheet to CSV |
soffice --cat in.docx |
The document's text on stdout, for a model to read |
--cat implies headless mode and cannot be combined with --convert-to. Its
output goes to the command result, which keeps up to 64 KiB of stdout. Redirect
longer documents to a file and read it with files.readText.
Convert many files side by side
LibreOffice keeps its settings in a user profile. To run conversions in
parallel inside one sandbox, give each conversion its own profile with -env:UserInstallation:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ image: "office", vcpu: 2, diskMiB: 8192 });await sbx.files.upload("./inbox", "/workspace/in");const inputs = (await sbx.files.glob("in/*.docx")).map((entry) => entry.path);await Promise.all( inputs.map((input, i) => sbx.exec( [ "soffice", `-env:UserInstallation=file:///tmp/lo-${i}`, "--headless", "--convert-to", "pdf", "--outdir", "/workspace/out", input, ], { check: true, timeoutMs: 600_000 }, ), ),);await sbx.files.download("/workspace/out", "./pdf");The whole out folder comes back as one compressed archive. For thousands of
files, spread them across sandboxes instead. After its first top-up, a paid
account runs 100 sandboxes at the same time, and an account
without credit runs four.
Start every sandbox with LibreOffice installed
This is the office image that the sample above uses. It is a
custom image. The recipe's packages install once, at build
time. If a larger set needs more room, the build machine's scratch disk can be
raised to 32 GiB.
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({ name: "office", recipe: { apt: [ "libreoffice-writer-nogui", "libreoffice-calc-nogui", "libreoffice-impress-nogui", "fonts-liberation", "fonts-crosextra-carlito", "fonts-crosextra-caladea", ], }, build: { diskMiB: 8192 },});Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build(name="office", recipe={"apt": [ "libreoffice-writer-nogui", "libreoffice-calc-nogui", "libreoffice-impress-nogui", "fonts-liberation", "fonts-crosextra-carlito", "fonts-crosextra-caladea",]}, build={"disk_mib": 8192})Building an image is free. Without credit, the build counts toward its hours. A stored image is charged by its size. An account without credit stores three images free.
Versions and limits
- Versions. 24.2.7 in
noble-updates; Ubuntu'snoble-backportspocket carried 26.2.5 on 25 October 2026, per Launchpad. - Disk. The default 4 GiB disk had about 2.5 GiB free on 24 September 2026.
Request
diskMiB: 8192when you install. Without credit, the disk can be up to 10 GiB. - Time. Set
timeoutMsfor installs and large decks. A command's default timeout is 60 seconds. - Network. Calling
network.off()after the install is the one line that keeps a hostile document from calling home. Downloads during the install use port 443, which a sandbox without credit reaches.
To go the other way, from Markdown to Word or PDF, see Pandoc in a sandbox. To pull text out of scanned PDFs, see Tesseract OCR in a sandbox. For why a microVM is the boundary here, see microVM vs container.
Sources
- LibreOffice help, command-line parameters, https://help.libreoffice.org/latest/en-US/text/shared/guide/start_parameters.html, read 25 October 2026
- Ubuntu 24.04 packages (libreoffice-*-nogui, fonts-liberation, fonts-crosextra-carlito, fonts-crosextra-caladea), from the noble package indexes at http://archive.ubuntu.com/ubuntu/dists/, read 25 October 2026
- Launchpad, published libreoffice sources for noble, https://launchpad.net/ubuntu/+source/libreoffice, read 25 October 2026
Facts on this page were checked on 25 October 2026.