Getting started

From zero to a compiled document.

nautris is self-hosted: it runs entirely in your infrastructure as a Docker image. This page takes you from an empty machine to parsing your first document.

On this page

Current versions

These are the versions we publish and run in production today. Pin them rather than tracking :latest: a pinned tag is the only way a redeploy cannot quietly change the software under a corpus you already compiled.

ComponentCurrent versionPull
Engine0.9.20registry.nautris.com/nautris:0.9.20
RAG generator0.5.2registry.nautris.com/nautris-rag-generator:0.5.2

The registry is public and read-only, so you can always check what exists without an account, rather than trusting this page to be current:

curl -s https://registry.nautris.com/v2/nautris/tags/list
curl -s https://registry.nautris.com/v2/nautris-rag-generator/tags/list
:latest is a convenience, not a contract. It moves whenever we publish, so two machines that pulled it a week apart are not running the same engine, and neither is the same machine before and after a docker compose pull. Use it to try nautris out. Once you have compiled anything you intend to keep, pin the version, so that re-running a corpus produces the same output it did last time.

Deploy with Docker

Pull the engine image straight from our registry, no account or login required, then deploy with a compose file. The image is public; the engine still needs a valid license to run (it refuses to start without one), so the license, not the registry, is the gate.

docker pull registry.nautris.com/nautris:0.9.20        # CPU, ~2.7 GB, amd64 + arm64
# docker pull registry.nautris.com/nautris:0.9.20-gpu  # only if you have an NVIDIA GPU
# docker-compose.yml - one image, two roles: API + N workers on a shared volume
services:
  api:
    image: registry.nautris.com/nautris:0.9.20
    restart: unless-stopped
    ports: ["8000:8000"]
    environment:
      NAUTRIS_LICENSE: ${NAUTRIS_LICENSE:?required}
      NAUTRIS_WORKERS: "0"      # the API enqueues...
      NAUTRIS_BACKGROUND: "1"   # ...the worker containers process
      NAUTRIS_RAG_DB: /data/lancedb
    volumes:
      - engine-data:/data
    read_only: true    # the API writes only to the volume and /tmp
    tmpfs: [/tmp]

  worker:
    image: registry.nautris.com/nautris:0.9.20
    command: ["python", "-m", "nautris.worker"]
    restart: unless-stopped
    environment:
      NAUTRIS_RAG_DB: /data/lancedb
    volumes:
      - engine-data:/data
    # Workers parse untrusted bytes: no privilege escalation, hard caps.
    security_opt:
      - no-new-privileges:true
    mem_limit: 8g     # isolates a heavy doc to one worker; large OCR PDFs peak past 4g
    pids_limit: 256

volumes:
  engine-data:

Which image to pull

The default tag is the CPU image, and it is what most self-hosters want: ~2.7 GB on disk, multi-architecture, and it runs the complete extraction pipeline. A GPU is an optimization, not a requirement. Pull the CUDA image only if you actually have an NVIDIA GPU, because it is more than twice the size.

TagContentsArchitecturesSize on disk
:latest, :X.Y.Z, :X.YCPU. Full extraction pipeline, no CUDAamd64 + arm64, resolved automatically~2.7 GB
:latest-gpu, :X.Y.Z-gpuCUDA build. Adds GPU acceleration and the opt-in NAUTRIS_VLM_OCR tieramd64 only~6-7 GB

That is the whole list: two tags to choose between, each pinnable by version. There is no architecture-suffixed tag to pull, because there is nothing to choose - :X.Y.Z is a multi-architecture manifest and docker pull resolves your architecture on its own. Confirm any tag yourself with docker manifest inspect registry.nautris.com/nautris:X.Y.Z, which lists every platform the tag really carries.

Upgrading from 0.9.11 or earlier? The unsuffixed tag used to be the CUDA build. It is now the CPU build, so a GPU deployment must move to :latest-gpu or :X.Y.Z-gpu. A GPU worker left on the unsuffixed tag keeps working, it just runs on the CPU.

Apple Silicon and ARM servers are covered by the default tag: docker pull resolves the arm64 build automatically, and no platform: line is needed. arm64 is CPU-only (the CUDA wheel index publishes nothing for aarch64), so NAUTRIS_VLM_OCR is unavailable there, but everything else works, including the full ML pipeline, because arm64 needs no AVX2. Do not force platform: linux/amd64 on a Mac: the emulated x86 image has no AVX2 under Rosetta, which limits it to born-digital documents.

Extraction models are fetched once on first run into the shared volume. Scale extraction with docker compose up --scale worker=4.

System requirements

No GPU is required: the core path is fully CPU-deterministic, and a GPU only accelerates the same OCR when one is present. What actually matters:

ResourceMinimumComfortableWhat consumes it
Disk~8 GB free20 GB+CPU image ~2.7 GB (the default) or CUDA image ~6-7 GB + model cache ~1.3 GB, fetched on first job + job artifacts. Budget ~12 GB minimum if you pull the CUDA image
RAM~8 GB (1 worker)16 GB+extraction peaks at 3–6 GB per worker (more on large / OCR-heavy PDFs); the API process is light. Budget per-worker RAM × worker count
CPU2 cores (any); AVX2 + FMA for OCR4+ cores, AVX2 + FMABorn-digital PDFs use the model-free fast path and run on any CPU - older x86, ARM, Apple Silicon. The ML / OCR path (scanned pages, complex layouts) needs AVX2 + FMA on x86: its runtime (onnxruntime/torch) is built for them, so Intel Haswell (2013)+ / AMD Zen (2017)+ is the floor there; ARM/Apple Silicon run it fine without AVX2. On an x86 chip without AVX2 the ML path is unavailable and those documents fail cleanly with a clear error (as of engine 0.9.2 - no longer a worker crash). Above the floor, cores are throughput only: native-text pages are fast, OCR pages run ~20–90 s/page
GPUoptional"GPU" here means NVIDIA CUDA, and it needs the -gpu image plus a GPU device reservation on the worker (10-30x faster on scanned / OCR-heavy PDFs, identical output). The CUDA image also falls back to the CPU on hosts without a GPU, so it is safe to run either way, just larger. Apple GPUs are not used: Docker on macOS runs containers in a Linux VM with no Metal passthrough, so a containerized engine on a Mac is CPU-only whatever the host hardware

Measured throughput: born-digital documents extract at roughly 280-300 pages/min on a single GPU worker (a 150-page report finishes in about 32 s); full GPU OCR on image-heavy scans runs around 20 pages/min. On the published benchmark corpus the engine scores 100% on tables, reading order, and headings.

Is the CPU image good enough for OCR?

Yes, and the output is the same. Both images run the same OCR pipeline with the same models and the same weights, so a scanned page yields the same text either way. A GPU does not read better, it reads faster. This is the one thing worth being precise about, because "CPU-only" is often assumed to mean "degraded", and here it does not.

What a GPU changes is throughput, and only really on scanned pages. Measured on the same documents, CPU on a 4-core laptop-class chip against a single NVIDIA worker:

DocumentCPU imageGPU imageDifference
Scanned page (OCR, full ML path)~80-85 s/page~6-14 s/pageroughly 6-14x
Born-digital page (model-free fast path)~3.7 s/page~1.4 s/pageroughly 2.6x
Text outputidentical

So the choice follows your corpus, not your quality bar:

A GPU is not a way to fix a bad scan. If OCR output is disappointing, a GPU will produce the same disappointing output faster. Resolution (300 dpi and up), straightness, and contrast move quality; hardware does not. NAUTRIS_OCR_LANGS is usually the more productive knob: a recogniser set to the wrong script is the most common cause of mangled Latin text.

When resources run short, failures stay contained and visible, never silent corruption:

Quickstart

A valid license is required: the server refuses to start without one (personal licenses are free and instant at get.nautris.com). There are no API keys to invent: the engine mints a default token on first boot, prints it in the startup logs, and shows it in /ui.

# Put the token in a .env file next to docker-compose.yml. Compose reads it
# automatically, so it survives a new shell and a reboot. (An exported shell
# variable works too, but only in the shell you exported it in.)
echo 'NAUTRIS_LICENSE=nl1....' > .env   # free personal license

docker compose up -d
docker compose logs api | grep "Default API token"
#   Default API token: dek_...

Parse a document and search it (send the token as X-API-Key):

# parse one file, get the canonical tree back in a single call
curl -X POST localhost:8000/v1/parse -H "X-API-Key: dek_..." -F "file=@mydoc.pdf" | jq .tree

# semantic search across everything indexed
curl -G localhost:8000/v1/search -H "X-API-Key: dek_..." --data-urlencode "q=your question" | jq '.[0]'

Then open the review dashboard at http://localhost:8000/ui. It signs itself in with the default token automatically; view, copy, or rotate the token on its API token page.

Next: harden the deployment and learn every knob in the operations guide, or open the API explorer and browse the full surface.