# Local Directory Source

> Indexing a folder already on the server in place, the allowed-roots security boundary, and what gets scanned and skipped.

- Updated: 2026-09-21
- Source: https://contextator.com/en/docs/local-directory-source/
- Language: en-US
- Author: Muhammet Şafak

---
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`:

```bash
# .env
DOCS_HOST_PATH=/srv/documentation
```

To mount several folders, add them to `docker-compose.yml` and list them in `ALLOWED_DOC_ROOTS`:

```yaml
volumes:
  - /srv/documentation:/docs:ro
  - /srv/handbook:/handbook:ro
```

```bash
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](/en/docs/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](/en/docs/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_GLOBS` patterns 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/reindex` from a cron job or a CI step — see [Admin API](/en/docs/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.
