Skip to content

Documents service ​

The documents service generates PDFs from Word documents (LibreOffice), redacts PDFs (PDFium), reads scans and photos (Tesseract OCR), and merges and splits PDFs. It is a separate image, ghcr.io/kuznyaoff/loopandroll-documents, shared by every instance of a host: instances call it over HTTP, it keeps nothing.

Without it, Loop & Roll works as before: the document nodes are not offered, and scanned attachments are reported as not read.

Run it ​

Add the service next to your instances, on a private network they share, and give each instance its address and a token:

yaml
services:
  documents:
    image: ghcr.io/kuznyaoff/loopandroll-documents:main
    restart: unless-stopped
    environment:
      DOCUMENTS_TOKENS: ${DOCUMENTS_TOKEN}
    mem_limit: 2g
    read_only: true
    tmpfs:
      - /tmp:size=1g
    networks: [documents]

  app:
    environment:
      DOCUMENTS_URL: http://documents:8080
      DOCUMENTS_TOKEN: ${DOCUMENTS_TOKEN}
    networks: [default, documents]

networks:
  documents:
    internal: true

internal: true is the important line: the service has no outbound access. LibreOffice never fetches an image linked by URL from a template, and nothing a document contains can make the service call anywhere.

Generate a token with openssl rand -hex 32 (32 characters at least).

Variables of the service ​

VariableDefaultMeaning
DOCUMENTS_TOKENS— (required)The accepted tokens, comma-separated (32 characters at least each). One per instance, or one per host; several allow a rotation without downtime.
PORT8080Listening port.
MAX_CONCURRENCY2Jobs at the same time. Beyond, a request waits QUEUE_WAIT_SECONDS, then gets 503 busy (the instance retries).
QUEUE_WAIT_SECONDS20How long a request waits for a free slot.
JOB_TIMEOUT_SECONDS120Time budget of one job; beyond, the job and its processes are killed (504 timeout).
JOB_MEMORY_MB1536Address space of one PDF job (PDFium, Tesseract). A document that needs more fails with too_complex.
MAX_DOCUMENT_MB25Size of one document.
MAX_REQUEST_MB70Size of a request body (base64 included).
MAX_PAGES100Pages of a PDF to redact or read.
MAX_PAGES_STRUCTURAL1000Pages of a PDF to merge or split.
MAX_MERGE_DOCUMENTS20PDFs in one merge.
RENDER_DPI200Resolution of redacted pages.
OCR_LANGUAGESfra+engTesseract languages (the image ships French and English).
LOG_LEVELinfodebug, info, warning, error.

Budget memory as MAX_CONCURRENCY × JOB_MEMORY_MB plus about 300 MB, and set the container limit accordingly.

Variables of the instance ​

VariableDefaultMeaning
DOCUMENTS_URL—The service address, for example http://documents:8080.
DOCUMENTS_TOKEN—One of the service tokens (DOCUMENTS_TOKEN_FILE works too). The URL and the token, or neither: one without the other is reported at startup and the service stays off.
DOCUMENTS_TIMEOUT_SECONDS150Time budget of one call, on the instance side. Keep it above the service's JOB_TIMEOUT_SECONDS.
DOCUMENTS_MAX_MB25Size of a document sent to the service.

What it keeps, what it logs ​

Nothing is stored: each job runs in its own process, in a temporary folder deleted at the end, with its own LibreOffice profile. The logs carry one line per request — route, status, error code, calling instance, sizes in bytes, duration — and never a document, a term searched or a file name.

Monitoring ​

  • GET /healthz answers 200 {"status":"ok"} without a token; the image has a Docker health check.
  • On the instance side, an unreachable or busy service makes document steps fail transiently: they are retried (three attempts). Invalid documents (password-protected PDF, malformed template) fail permanently, with a reason.
  • A 401 from the service means the instance's token is not in DOCUMENTS_TOKENS: the steps fail with document.not_configured.

Troubleshooting ​

SymptomCause
The nodes do not appear in the paletteDOCUMENTS_URL or DOCUMENTS_TOKEN is missing on the instance (see the startup warnings).
document.service_unavailable on every runThe instance cannot reach the service: network, name, port, or the service is down.
Many 503 busyRaise MAX_CONCURRENCY (and the memory limit), or add a replica.
too_complex on a large scanRaise JOB_MEMORY_MB, or lower RENDER_DPI.
Generated PDFs lay out differently from WordA font of the template is missing: the image ships DejaVu, Liberation, Carlito and Caladea (metric equivalents of Calibri and Cambria).

Loop & Roll — the workflow builder for email.