Skip to content

Deployment

Rendlio is a single self-contained CLI — no Office, no LibreOffice, no network — which makes deployment mostly a question of where you run the binary and what your pipeline does with the exit code. This page covers the common patterns.

The container image wraps the exact same rendlio binary as the .NET global tool — same flags, same exit codes, same reports. Mount your working directory and convert:

Terminal window
docker pull ghcr.io/rendlio/rendlio
docker run --rm -v "$PWD:/work" -w /work ghcr.io/rendlio/rendlio \
convert book.xlsm -o book.pdf --format pdfa --report report.json

Or pipe through stdin/stdout without mounting anything — stdout carries only document bytes; all diagnostics go to stderr:

Terminal window
cat in.xlsx | docker run --rm -i ghcr.io/rendlio/rendlio convert - -o - > out.pdf

Three equivalent options, in the engine’s normal discovery order:

Terminal window
# 1. Mount the file into the working directory (cwd discovery)
docker run --rm -v "$PWD:/work" -w /work ghcr.io/rendlio/rendlio convert in.xlsx -o out.pdf
# 2. Pass it inline — RENDLIO_LICENSE accepts a path OR the licence text itself
docker run --rm -e RENDLIO_LICENSE="$(cat Rendlio.license)" -v "$PWD:/work" -w /work \
ghcr.io/rendlio/rendlio convert in.xlsx -o out.pdf

Or bake Rendlio.license into a derived image at ~/.rendlio/Rendlio.license. The licence is verified offline — the container needs no network access, ever.

The engine’s own memory cap (--max-memory) is a cooperative soft cap enforced at checkpoints. Always set a hard container limit as the backstop for spikes between checkpoints:

Terminal window
docker run --rm --memory 2g --cpus 2 -v "$PWD:/work" -w /work \
ghcr.io/rendlio/rendlio convert big.xlsx -o big.pdf --timeout 120

CI gating keys on the exit code, which is the report’s verdict as a number (normative, unchanged from first public release):

CodeMeaningContract
0cleanNo warnings, nothing approximated/unsupported
1rendered with warningsOutput written; report has warnings/approximated
2unsupported contentOutput written (one exception: a --recalculate=strict-v1 refusal produces no document — scripts using strict-v1 must check result.status); report lists unsupported entries
3invalid input / invalid usageNo output. Message says exactly what and the remedy
4resource limitPartial outputs deleted; message names the limit and the override env/flag
5internal errorBug. Message asks to file an issue with --verbose log; never silently swallowed

Scripting contract: exit <= 2 ⇒ the document exists and is complete per the report (strict-v1 refusal excepted, see the exit-2 row); CI users typically treat <=1 as pass, 2 as review. On exit 4 partial outputs are deleted — you never gate on a truncated document — and the report is still written, naming the limit that fired.

For reproducible pipelines, add --deterministic (identical input + options ⇒ identical bytes) and pin --reference-date so date-dependent conditional formats do not shift between runs.

name: render-reports
on: [push]
jobs:
convert:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rendlio
run: dotnet tool install -g Rendlio.Tool
- name: Convert with report
shell: bash
run: |
set +e
rendlio convert reports/monthly.xlsm -o monthly.pdf \
--format pdfa --report report.json \
--deterministic --reference-date 2026-08-01
code=$?
set -e
if [ "$code" -gt 1 ]; then
echo "::error::conversion not clean (exit $code) — see report.json"
exit "$code"
fi
- name: Gate on the report (optional, stricter)
run: jq -e '.fonts.substitutions | length == 0' report.json
- name: Upload the report
if: always()
uses: actions/upload-artifact@v4
with:
name: compat-report
path: report.json

The set +e / $? dance matters: Actions fails a step on any non-zero exit, and exit 1 (rendered with warnings) is usually a pass in practice. Capture the code, then apply your policy. More report-level gates (fail on any unsupported, allowlist specific warning codes, schema validation) are on the compatibility report page.

GitLab can natively surface exit 2 as a warning-level job instead of a failure via allow_failure: exit_codes:

render-pdf:
image:
name: ghcr.io/rendlio/rendlio
entrypoint: [""]
script:
- |
rendlio convert reports/monthly.xlsm -o monthly.pdf \
--format pdfa --report report.json --deterministic || code=$?
code=${code:-0}
if [ "$code" -le 1 ]; then exit 0; fi # 0 clean, 1 warnings → pass
exit "$code" # 2 → warning job (below); >=3 → fail
allow_failure:
exit_codes: [2]
artifacts:
when: always
paths:
- monthly.pdf
- report.json

artifacts.when: always keeps report.json even on failing runs — the report is written on every outcome, including limit hits, and it names exactly what went wrong.

Rendlio is designed to run where the network does not exist:

  • Zero network I/O, categorically. The engine performs no activation, no telemetry, no font downloads, no phone-home — a contractual promise, enforced in the codebase by a static analyzer that bans network APIs outright.
  • Install without a feed. Self-contained archives per platform need neither the .NET SDK nor a package feed — copy, unpack, run. Checksums publish alongside every archive on Downloads.
  • Licences verify offline. The licence file is checked by signature against public keys embedded in the binary. Copy the file in with the binary, or carry it inline in RENDLIO_LICENSE. rendlio license info tells you on the spot whether the build is covered.
  • Perpetual fallback. Builds released within your maintenance window keep working forever — an air-gapped host never needs to reach anything to stay licensed.
  • Fonts resolve locally. Bundled fonts are used by default (deterministic everywhere); corporate fonts come from local directories via --fonts-dir or RENDLIO_FONTS_DIR. --use-system-fonts is off by default.

If you embed Rendlio in a path where anyone can upload a workbook (the public playground runs exactly this configuration), set RENDLIO_SANDBOX=1. It hard-disables user font directories and system fonts (bundled fonts only), refuses --recalculate=strict-v1, and tightens the default timeout to 60 s. Any option the sandbox forbids fails loudly with W8112 and exit 3 — never silently ignored. Pair it with an explicit RENDLIO_MAX_MEMORY and a lower RENDLIO_MAX_FILE_SIZE (below), plus a hard container limit.

Every quantitative limit in the engine has a default and an environment override; the timeout and memory cap also have CLI flags. Precedence: flag > environment variable > default. The effective values are echoed into report.options, so a report always shows what actually applied.

Two rules worth knowing before you tune anything:

  • Overrides are never clamped or rounded. An unparseable value (signs, separators, fractions, units) is a usage error, exit 3 — a limit silently weaker than you asked for would be worse than a refusal. Unset or empty means “default”.
  • A limit hit never leaves a partial document. On breach the run exits 4, temp outputs are deleted, and the report is still written naming the limit, its configured value, and the measured value.
LimitEnv overrideCLI flagDefault
Input file sizeRENDLIO_MAX_FILE_SIZE500 MB
ZIP entry countRENDLIO_MAX_ZIP_ENTRIES10,000
Total uncompressed bytesRENDLIO_MAX_UNCOMPRESSED_TOTAL2 GB
Per-entry uncompressed bytesRENDLIO_MAX_ENTRY_UNCOMPRESSED512 MiB
Per-entry compression ratioRENDLIO_MAX_COMPRESSION_RATIO1000:1 (past a 1 MiB floor)
XML element depthRENDLIO_MAX_XML_DEPTH128
Shared-string entriesRENDLIO_MAX_SHARED_STRINGS2,000,000
Single string lengthRENDLIO_MAX_STRING_LENGTH32,767 chars (the file format’s own cell-text cap)
Style recordsRENDLIO_MAX_STYLES65,490 (the format’s own cap)
Merged rangesRENDLIO_MAX_MERGED_RANGES100,000
Images per sheetRENDLIO_MAX_IMAGES_PER_SHEET1,000
Pixels per image decodeRENDLIO_MAX_IMAGE_PIXELS100,000,000 (100 MP) — an over-cap image is skipped with a warning; the conversion continues
Sheet countRENDLIO_MAX_SHEETS1,000
Used-range cells (workbook total)RENDLIO_MAX_CELLS50,000,000
Wall-clock timeoutRENDLIO_TIMEOUT (seconds)--timeout300 s (60 s under RENDLIO_SANDBOX=1)
Memory soft capRENDLIO_MAX_MEMORY (bytes)--max-memory (MB)see note below

Memory-cap default: under review (FS-10 OPEN-3) — the CLI contract prints 1024 MB while the engine’s canonical limits registry applies unset (no cap) unless one is given. Set it explicitly if you rely on it, and set a hard container limit either way (the soft cap is checkpoint-based; the container limit is the backstop).

Recalculation (--recalculate=strict-v1, planned) adds one more: RENDLIO_MAX_RECALC_CELLS, default 10,000,000 cells in the evaluation closure.

View as Markdown