A folder that is already on the server — mounted into the container — scanned in place. Nothing is copied, nothing is modified, and edits show up on the next index run.
This is the simplest source and the right one when the documentation is already on the machine: a repository checkout, a synced folder, a network share, an Obsidian vault on the same host.
Making the folder visible to the container
The container can only see what you mount. With Compose, DOCS_HOST_PATH is mounted read-only at
/docs:
# .env
DOCS_HOST_PATH=/srv/documentation
To mount several folders, add them to docker-compose.yml and list them in ALLOWED_DOC_ROOTS:
volumes:
- /srv/documentation:/docs:ro
- /srv/handbook:/handbook:ro
ALLOWED_DOC_ROOTS=/docs,/handbook
Adding the source
Add source → Local directory
| Field | Example | Notes |
|---|---|---|
| Name | handbook |
The mount prefix of every path from this folder. Immutable |
| Directory | /docs/ + handbook |
The dashboard shows the allowed root as a prefix; you type the rest |
| Content type | Plain Markdown / text |
Pick Obsidian vault for a vault — see Obsidian Vaults — or OpenAPI / Swagger for a folder of specifications |
| File types | .md, .mdx |
Add .txt, .html/.htm, .csv, .docx or .pdf if you want them too — everything but the first three is converted to Markdown as it is indexed |
When several allowed roots are configured, the prefix becomes a dropdown.
The allowed-roots rule
A local source’s directory must resolve inside one of ALLOWED_DOC_ROOTS. The path is resolved
through symlinks first, so:
..escapes are rejected;- a symlink pointing outside an allowed root is rejected;
- a path that is not a directory is rejected.
If you see “Directory is outside the allowed document roots”, either use a path under an allowed root
(inside Docker that means /docs/...), or add the root to ALLOWED_DOC_ROOTS and restart.
This is a genuine security boundary, not a convenience check — see Security.
What is scanned
Starting at the directory, recursively:
- only the file types the source selected;
- dotfiles, dot-directories,
node_modules,dist,build,vendor,__pycache__are skipped; - symlinks that escape the directory are skipped;
IGNORE_GLOBSpatterns are skipped.
Paths keep their structure and gain the source name: guides/install.md inside a source named
handbook is indexed, searched and read as handbook/guides/install.md.
Keeping it up to date
There is nothing to fetch — the folder is read at index time. Trigger a run when the files change:
- Sync on the source row, or Re-index in the project header;
- or
POST /api/projects/:id/reindexfrom a cron job or a CI step — see Admin API.
Because indexing is incremental, running it often is cheap: unchanged files are skipped by hash.
Read-only mounts
Mount your documentation read-only (:ro), as the shipped compose file does. Contextator never writes
to a local source, and the mount makes that guarantee structural.