Skip to content

Install

This page shows you how to run the service with the right container flags, with Compose, and from source, and how to run the CLI. It ends with what the machine needs.

The image is ghcr.io/vmosoti/rusticdoc:0.1.0, for amd64 and arm64. While the repository is private, so is the image: sign in with docker login ghcr.io and a GitHub token that can read packages before you pull it. You can also build it from a checkout of the repository:

Terminal window
docker build -t rusticdoc .

The build pins its base images by digest. The result is one Rust binary on Debian 13 with LibreOffice, its Python bridge, qpdf and pdfcpu. It has no Node, Java or Chromium. It runs as user 1001 and listens on port 9027.

Terminal window
docker run -d --name rusticdoc \
--read-only --cap-drop ALL --security-opt no-new-privileges \
--tmpfs /tmp --tmpfs /home/rusticdoc:uid=1001,gid=1001 \
-p 127.0.0.1:9027:9027 ghcr.io/vmosoti/rusticdoc:0.1.0
Flag Why
--read-only A compromised engine cannot change the image’s files
--cap-drop ALL The service binds an unprivileged port and needs no Linux capabilities
--security-opt no-new-privileges Nothing inside can gain privileges through a setuid binary
--tmpfs /tmp Scratch space for conversions. With --read-only and no /tmp, a conversion answers 500
--tmpfs /home/rusticdoc:uid=1001,gid=1001 A writable home directory for the engines. A DOCX conversion also worked without it, but Compose and the Kubernetes manifest both provide one, so that is the setup to copy
-p 127.0.0.1:9027:9027 Publishes the port on this machine only

Do not add --init. The service runs as PID 1 so that it can close its /proc/1/environ to the user LibreOffice runs as. With --init, PID 1 is docker-init, and its environment, which holds your keys, is readable from inside the container.

With no credentials, the service answers anyone and warns at every start. Set RUSTICDOC_API_KEYS before you publish the port more widely. Docker has more, including limits on memory and processes.

deploy/compose.yaml sets the same flags and adds limits on memory, processes and temporary files. It reads its settings from your environment. From the root of a checkout:

Terminal window
docker compose -f deploy/compose.yaml up -d --build

To use the published image instead of building one:

Terminal window
RUSTICDOC_IMAGE=ghcr.io/vmosoti/rusticdoc:0.1.0 \
docker compose -f deploy/compose.yaml up -d --no-build

Docker lists the variables it reads.

You need the Rust toolchain that rust-toolchain.toml pins, which is 1.99.1. rustup installs it for you.

Terminal window
cargo build --release -p rusticdoc
./target/release/rusticdoc serve --bind 127.0.0.1:9027

The binary looks for its engines at fixed paths, not on your PATH. Set RUSTICDOC_LIBREOFFICE_BIN, RUSTICDOC_QPDF_BIN and RUSTICDOC_PDFCPU_BIN if yours are elsewhere. LibreOffice also needs its Python UNO bridge, which is the python3-uno package on Debian. The configuration reference lists the paths it tries.

rusticdoc engines shows what it found. An engine that is missing fails only the steps that need it, with 503. On a Mac with no engines installed, text templates rendered and a convert step answered 503. The render memory limit relies on Linux and has not been shown to hold on macOS, so do not serve untrusted templates from a Mac.

Each release attaches the CLI for Linux and macOS to its GitHub release, with a SHA256SUMS file:

System Archive
Linux, x86-64 rusticdoc-0.1.0-x86_64-unknown-linux-gnu.tar.gz
Linux, ARM64 rusticdoc-0.1.0-aarch64-unknown-linux-gnu.tar.gz
macOS, Apple silicon rusticdoc-0.1.0-aarch64-apple-darwin.tar.gz
macOS, Intel rusticdoc-0.1.0-x86_64-apple-darwin.tar.gz

While the repository is private, download with the GitHub CLI, which uses your sign-in. This fetches the Apple silicon build, checks it and puts it on your PATH:

Terminal window
gh release download v0.1.0 --repo vmosoti/rusticdoc \
--pattern 'rusticdoc-0.1.0-aarch64-apple-darwin.tar.gz' --pattern SHA256SUMS
shasum -a 256 --check --ignore-missing SHA256SUMS
tar -xzf rusticdoc-0.1.0-aarch64-apple-darwin.tar.gz
sudo mv rusticdoc-0.1.0-aarch64-apple-darwin/rusticdoc /usr/local/bin/
rusticdoc --version

On Linux, sha256sum --check --ignore-missing SHA256SUMS does the check.

  • macOS marks a downloaded binary as quarantined, and the builds are not notarised. If it refuses to open, run xattr -d com.apple.quarantine /usr/local/bin/rusticdoc.
  • Linux builds need glibc 2.35 or newer, as on Ubuntu 22.04 and Debian 12, and fontconfig for HTML layout to find fonts.
  • Windows has no build: the CLI’s process handling is Unix-only. Run the Linux build under WSL, or use the image as below.

The downloaded CLI renders, validates and runs the PDF operations its own engine covers. convert steps need LibreOffice installed, and some PDF operations need qpdf or pdfcpu, as build from source describes. rusticdoc engines shows what it found.

The service and the CLI are the same binary, so the image has the CLI in it:

Terminal window
docker run --rm --entrypoint rusticdoc ghcr.io/vmosoti/rusticdoc:0.1.0 engines

To work on files in your current directory, mount it. The container’s user has to be able to write there, so on Linux pass your own user ID. Group 0 owns the home directory, so keep that group:

Terminal window
docker run --rm -v "$PWD:/work" -w /work --user "$(id -u):0" \
--entrypoint rusticdoc ghcr.io/vmosoti/rusticdoc:0.1.0 \
render invoice.docx --data invoice.json --steps convert -o invoice.pdf

From a source build, cargo build --release -p rusticdoc gives you the same commands in target/release/rusticdoc.

The CLI reads --data from a file.

The CLI renders templates in its own process, with no separate worker and no memory limit, because it assumes the template is yours. serve isolates them. Run untrusted templates through the service. The CLI reference lists the commands.

What is known
Container runtime Docker. CI builds and smoke-tests the image on ubuntu-latest. The examples in these docs ran on an arm64 build
Disk About 300 MB to download, unpacking to about 810 MB, measured on the arm64 build. Docker Desktop keeps both copies and shows about 1.1 GB
Memory at rest 150 to 270 MiB in docker stats, measured after a first conversion with two LibreOffice workers. This grows with what you ask of it
Memory under load Compose limits the container to 6 GiB, which leaves room for four render workers at 1 GiB each, the service and two LibreOffice workers. Sizing has the arithmetic and a failure seen at less
CPU The Kubernetes manifest requests one CPU per pod. No minimum has been measured
Operating system for the service Linux, for the memory limit on render workers. The image is Linux
Rust, from source 1.99.1, pinned in rust-toolchain.toml

There are no load test figures yet. Size for your own documents and measure them.