---
title: "Environment variables"
description: "The full table of instance environment variables: mode and port, spaces and the metadata database, semantic search, the Docker image."
---

# Environment variables

Notarium is configured through environment variables. The defaults just work — for a local run you don't need to fill in anything: copy `.env.example` to `.env` and edit the lines you actually need. The Docker stack passes `.env` into the container as-is (values are not baked into the image), so the same file describes both your local and your production instance.

```bash
cp .env.example .env      # the defaults work — edit only what you need
```

Below is the full reference. Some variables are set directly in `.env.example`; others (search fine-tuning) have defaults in code and aren't spelled out in the example — those are flagged separately.

## Core

The basics: port, authentication mode, and the location of the metadata database and spaces.

| Variable | Purpose | Default | Example |
|---|---|---|---|
| `DATA_DIR` | The single data knob: the root from which everything else is derived — the metadata database, indexes, notes, artifacts. Unset → a sensible default is used. | `/data` (Docker); `~/.local/share/notarium` (host) | `DATA_DIR=/srv/notarium` |
| `PORT` | The port the backend listens on; a single Fastify listener serves `/api`, `/mcp`, and the SPA's static assets. | `3000` | `PORT=3000` |
| `AUTH_MODE` | Authentication mode: `password` (login plus a first-run setup screen, requires the metadata database) or `none` (a single all-access principal for desktop/dev/trusted intranet, no login UI). | `password` | `AUTH_MODE=none` |
| `META_DB_URL` | The metadata database: identity, revision log, space registry, authentication, projects. Defaults to sqlite under `DATA_DIR`, so `password` mode works with no setup. Optional: set it only to move metadata state into an external Postgres (shared state, HA). | `sqlite:<DATA_DIR>/meta.db` | `META_DB_URL=postgres://user:pass@db:5432/notarium` |
| `SPACES_ROOT` | The root where each space is a folder; enables creating spaces from the UI at runtime. Optional: defaults to `<DATA_DIR>/spaces`, set it only if your notes live outside the data root. | `<DATA_DIR>/spaces` | `SPACES_ROOT=/mnt/notes` |
| `SPACES_CONFIG` | Explicit space topology: inline JSON or a path to a JSON file. Overrides the single-space variables. | unset | `SPACES_CONFIG=/data/spaces.json` |
| `ENGINE_DATA_DIR` | Where the engine keeps derived indexes — one file per space. The file name follows the space's folder name and doesn't change when the space is renamed. Deleting the directory → a reindex at startup; the index is recoverable. Optional: defaults to `<DATA_DIR>/engine`, set it to move the indexes onto another disk. | `<DATA_DIR>/engine` | `ENGINE_DATA_DIR=/mnt/ssd/engine` |
| `JOBS_DATA_DIR` | The job directory: artifacts of async exports (derived, cleaned up by TTL) **and** the uploaded files of an unfinished import — those live exactly as long as their job does, which is why they belong in a backup. Optional: defaults to `<DATA_DIR>/jobs`, set it to move it onto another disk. | `<DATA_DIR>/jobs` | `JOBS_DATA_DIR=/mnt/ssd/jobs` |
| `SPACE_IDLE_EVICT_SECONDS` | Evict the read model of an idle space. `0` — keep it warm; spaces with a live SSE connection are never evicted. | `0` | `SPACE_IDLE_EVICT_SECONDS=900` |
| `SYNC_POLL_SECONDS` | The polling interval for external on-disk changes (each poll is a full space rescan). `0` — disable polling. For unwatchable mounts (network volume, in-memory) the effective interval is capped at 60 s. | `120` | `SYNC_POLL_SECONDS=0` |
| `PUBLIC_BASE_URL` | The canonical external address of the instance behind a reverse proxy — for the OAuth metadata of MCP connectors. Without it, the address is derived from the proxy's forwarded headers. | unset | `PUBLIC_BASE_URL=https://notes.example.com` |
| `TRUST_PROXY` | A comma-separated list of IPs/CIDRs of the **immediate** proxies — used to derive the real client IP for login rate limits and for admitting new OAuth clients. Unset means the safe default: `X-Forwarded-For` has no effect on the limits. Boolean values, hop counts, named ranges, and all-address ranges (`/0`) are rejected at startup. | unset | `TRUST_PROXY=172.18.0.0/16` |

> [!note] Metadata database vs. files
> Two different things live on disk. `SPACES_ROOT` is the **Markdown truth** (your notes, one folder per space). `META_DB_URL` is the metadata database: what can't be derived from the files (users, access, version history). More in the [Self-host](/docs/self-hosting/) section.

## Single-space (bare-host, no Docker)

For running a single space without `SPACES_CONFIG` and without `SPACES_ROOT` (for example, a local bare run without Docker).

| Variable | Purpose | Default | Example |
|---|---|---|---|
| `ENGINE` | The engine for a single space. The only value is `notarium`; you can leave it unset. | `notarium` | `ENGINE=notarium` |
| `NOTES_DIR` | Absolute path to the notes folder of a single space (single-space mode). | unset | `NOTES_DIR=/home/me/notes` |

## Semantic search

Lexical full-text search (FTS) always works, with no configuration. Semantic (vector) and hybrid search are **opt-in**: a heavy native stack (`onnxruntime` + `sqlite-vec`, ~660 MB on disk) plus the bge-m3 embedding model (~600 MB on disk, hundreds of MB of RAM). The variables below have defaults in code and aren't spelled out in `.env.example`.

| Variable | Purpose | Default | Example |
|---|---|---|---|
| `VECTOR_SEARCH` | `on`/`off` — turns on semantics and hybrid fusion. When the native stack is missing, `on` falls back to full-text search — without error. | `on` (code), `off` (published image) | `VECTOR_SEARCH=on` |
| `EMBED_MODEL` | The id of the embedding model (transformers.js/ONNX). Set it **together** with `EMBED_DIMENSIONS`. | `Xenova/bge-m3` | `EMBED_MODEL=Xenova/multilingual-e5-small` |
| `EMBED_DIMENSIONS` | The vector width; it **must** match the model (bge-m3 — 1024, e5-small — 384). A mismatch is fail-closed: the note stays FTS-only. | `1024` | `EMBED_DIMENSIONS=384` |
| `EMBED_DTYPE` | Model quantization: `fp32` / `fp16` / `q8` / `q4`. | `q8` | `EMBED_DTYPE=fp16` |
| `EMBED_THREADS` | The number of ONNX intra-op threads per background-indexing worker (a pool of `EMBED_WORKERS` workers). | `1` per worker (no-pool fallback — half the cores) | `EMBED_THREADS=2` |
| `EMBED_WORKERS` | The size of the embedding `worker_threads` pool = background-indexing parallelism across cores. Each worker holds its own copy of the model (this affects RAM). | `max(1, min(cores−2, 4))` | `EMBED_WORKERS=8` |
| `EMBED_QUERY_PREFIX` / `EMBED_PASSAGE_PREFIX` | Prefixes for asymmetric models (e5). For the symmetric bge-m3, **leave them unset** — otherwise quality drops in ways you won't notice. | unset | `EMBED_QUERY_PREFIX="query: "` |
| `EMBED_CPU_MEM_ARENA` | `on`/`off`. `off` keeps consumption flat at ~1.9 GB of RAM for bge-m3 — a guard against OOM on a tight, swapless machine (with `on` the arena can creep up to several GB). | `on` | `EMBED_CPU_MEM_ARENA=off` |
| `GRAPH_BOOST` | `on`/`off` — a third RRF channel (a graph boost over links, 1-hop wikilink). Inert when `VECTOR_SEARCH=off`. | `off` | `GRAPH_BOOST=on` |

> [!warning] Two independent switches
> For semantics to work locally you need **both**: the native stack **installed** (`make deps-vector`; the default `make deps` does not install it, the published image always ships it) **and** `VECTOR_SEARCH=on`. If the stack is missing, `on` falls back to lexical full-text search — without error. More in the [Search](/docs/concepts/search/) and [Search setup](/docs/self-hosting/search-setup/) sections.

## Backup and restore

The built-in `backup`, `backup verify`, and `restore` commands work with no configuration. The variables below only matter when the container root is mounted read-only or your data is noticeably larger than typical. More in [Backup and restore](/docs/self-hosting/backup/).

| Variable | Purpose | Default | Example |
|---|---|---|---|
| `NOTARIUM_BACKUP_TMPDIR` | The directory for the intermediate files of a backup, a verify, or a restore. Set it if the container root is read-only or `/tmp` is short on space: a streaming backup may temporarily need room for the archive plus two expanded stages. | `/tmp` | `NOTARIUM_BACKUP_TMPDIR=/mnt/scratch` |
| `NOTARIUM_BACKUP_MAX_BYTES` | The size ceiling — for both the compressed input and the expanded payload. A zip-bomb guard; only large, trusted installations raise it. | 64 GiB | `NOTARIUM_BACKUP_MAX_BYTES=137438953472` |
| `NOTARIUM_BACKUP_MAX_ENTRIES` | The ceiling on the number of entries in the archive. | `1000000` | `NOTARIUM_BACKUP_MAX_ENTRIES=2000000` |
| `NOTARIUM_BACKUP_MAX_METADATA_BYTES` | A separate memory ceiling for names, the ZIP's internal structures, and `manifest.json`. | 32 MiB | `NOTARIUM_BACKUP_MAX_METADATA_BYTES=67108864` |

## Docker and build

| Variable | Purpose | Default | Example |
|---|---|---|---|
| `IMAGE` / `TAG` | The image reference for `docker compose` / `make up`. The main install path is the public image `docouno/notarium:latest`; override the coordinate to use your own registry or a specific tag. | `docouno/notarium:latest` | `IMAGE=docouno/notarium` `TAG=latest` |
| `GIT_SHA` / `BUILD_TIME` | Build-args; inlined into `GET /api/about` and the **Settings → About** tab. Without them — `null`. | empty | `GIT_SHA=$(git rev-parse --short HEAD)` |

> [!important] The image and building from source
> `docouno/notarium:latest` is the public image and the main install path; `IMAGE` / `TAG` set which image `docker compose` / `make up` will pull. If the image hasn't been pulled from the registry yet — build from source in the main repository: `make up` (the behavior is identical).

## See also

- [Self-host configuration](/docs/self-hosting/configuration/) — installation, volumes, production configuration.
- [Search](/docs/concepts/search/) — lexical, semantic, hybrid fusion, and the fallback to full-text search without error.
- [Keyboard shortcuts reference](/docs/reference/keyboard-shortcuts/) — layouts and presets.
