Notes are personal infrastructure.
You can keep them in a hosted workspace, sync them through a vendor account, or store them as plain files on a machine you control.
NoteDiscovery is in that third group: a self-hosted Markdown knowledge base with a modern browser interface, local file storage, search, backlinks, graph view, themes, plugins, sharing, export, and MCP support for AI assistants.
What is NoteDiscovery?
NoteDiscovery is a web app for reading, writing, organizing, and discovering Markdown notes.
The important part is the storage model. Your notes live as plain files under a data directory. The app gives you a browser UI on top of that folder, but it does not force your knowledge base into a hosted database or proprietary export format.
The project includes:
- Markdown note editing and preview.
- Folder organization.
- Full-text search.
- Tags.
- Backlinks and graph relationships.
- Favorites.
- Themes.
- Templates.
- Media uploads.
- Public share links.
- HTML export and print support.
- A drawing editor that saves PNG files next to notes.
- A plugin system.
- A built-in MCP server for AI tools.
That makes it interesting for homelab users who want something lighter than a team wiki, more web-native than a folder of raw Markdown files, and easier to deploy than a database-backed notes platform.
Tech Overview
NoteDiscovery is a Python/FastAPI application.
The backend lives under backend/. It loads config.yaml, overlays selected environment variables, reads the VERSION file, configures FastAPI, serves static frontend assets, protects routes when authentication is enabled, and exposes APIs for notes, folders, media, search, graph, themes, plugins, sharing, export, locales, config, and health.
The frontend lives under frontend/. It is a static single-page app with a large JavaScript client, dynamic theme loading, Markdown preview, upload handling, local UI preferences, editor settings, PWA metadata, and a service worker.
There is no database server. Notes and uploaded files live under data/ by default. Share tokens and plugin state are also file-backed.
The in-memory index is worth calling out. backend/note_index.py tracks notes, folders, tags, internal links, backlinks, graph edges, and search terms under a thread-safe lock. The structural index and search index are separate, so the app can rebuild or update vault state without turning every request into a full filesystem scan.
AI and MCP Support
NoteDiscovery includes an MCP server under mcp_server/.
This server speaks stdio JSON-RPC to MCP-compatible clients and calls the NoteDiscovery HTTP API behind the scenes. It can search notes, list notes, read notes, create and update notes, append content, move notes, create folders, list tags, inspect backlinks, fetch the graph, and create notes from templates.
The basic Docker-based MCP command from the upstream README looks like this:
{
"mcpServers": {
"notediscovery": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-e",
"NOTEDISCOVERY_URL=http://host.docker.internal:8000",
"ghcr.io/gamosoft/notediscovery:latest",
"python",
"-m",
"mcp_server"
]
}
}
}
For authenticated deployments, also pass NOTEDISCOVERY_API_KEY.
Self-Hosting NoteDiscovery with Docker
Pre-Requisites - Docker
Install Docker on your system before proceeding:
- Linux: Official Docker Engine install guide
- Windows / Mac: Docker Desktop
Verify installation: docker --version && docker compose version
The upstream project provides two main Compose paths:
docker-compose.ghcr.ymlfor the prebuilt GHCR image.docker-compose.ymlfor building from local source.
For most self-hosted installs, use the GHCR image.
The upstream compose maps host port 8000 and uses container_name: notediscovery. For a homelab where port 8000 may already be taken, I prefer a more explicit, collision-resistant compose.
services:
notediscovery:
image: ghcr.io/gamosoft/notediscovery:latest
container_name: notediscovery
restart: unless-stopped
ports:
- "18080:8000"
environment:
TZ: ${TZ:-UTC}
APP_NAME: ${APP_NAME:-NoteDiscovery}
AUTHENTICATION_ENABLED: ${AUTHENTICATION_ENABLED:-true}
AUTHENTICATION_PASSWORD: ${NOTEDISCOVERY_PASSWORD:?Set NOTEDISCOVERY_PASSWORD in .env}
AUTHENTICATION_SECRET_KEY: ${NOTEDISCOVERY_SECRET_KEY:?Set NOTEDISCOVERY_SECRET_KEY in .env}
AUTHENTICATION_API_KEY: ${NOTEDISCOVERY_API_KEY:-}
SHARE_PUBLIC_ORIGIN: ${SHARE_PUBLIC_ORIGIN:-}
volumes:
- ./data:/app/data
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 60s
timeout: 3s
retries: 3
start_period: 5s
Create the data folder:
mkdir -p data
Generate a session secret:
python3 -c "import secrets; print(secrets.token_hex(32))"
Create .env:
TZ=Europe/Warsaw
APP_NAME=My Notes
NOTEDISCOVERY_PASSWORD=replace-this-password
NOTEDISCOVERY_SECRET_KEY=replace-this-with-a-generated-secret
NOTEDISCOVERY_API_KEY=
SHARE_PUBLIC_ORIGIN=
AUTHENTICATION_ENABLED=true
Validate and start:
docker compose config
docker compose up -d
Then open:
http://localhost:18080
From another machine on your LAN, replace localhost with your server IP.
Field Note: Docker Smoke Test
I ran a bounded container test without touching existing containers.
The trial used:
- Compose project:
notediscovery-foss-post-trial - Container name:
notediscovery-foss-post-trial - Host port:
18080 - Data bind mount:
/tmp/foss-post/notediscovery-trial/data - Docker network: existing
bridge - Restart policy:
no
The test started ghcr.io/gamosoft/notediscovery:latest, then:
curl http://localhost:18080/health
returned:
{"status":"healthy","app":"NoteDiscovery Foss Post Trial","version":"0.31.4"}
I also created a Markdown note through the API:
curl -X POST http://localhost:18080/api/notes/codex-smoke.md \
-H "Content-Type: application/json" \
-d '{"content":"# Codex Smoke Test\n\nThis note validates NoteDiscovery Docker deployment, search, and API access.\n\nTags: #docker #selfhosted"}'
Then searched for it:
curl "http://localhost:18080/api/search?q=Docker"
The API returned the smoke-test note and matching lines. The file was also present on disk under the temporary data/ directory.
After the test, I stopped and removed only the trial container:
docker compose \
-f /tmp/foss-post/notediscovery-trial/docker-compose.yml \
-p notediscovery-foss-post-trial \
down --remove-orphans
No existing containers were modified.
One site workflow caveat: /home/jalcocert/Desktop/Home-Lab was not available on this machine, so I could not add a reusable public Home-Lab compose snippet for this post.
Configuration Notes
The most important environment variables are:
| Variable | Use |
|---|---|
APP_NAME |
Name shown in the UI and login page |
NOTES_DIR |
Alternate vault path |
AUTHENTICATION_ENABLED |
Turn auth on/off |
AUTHENTICATION_PASSWORD |
Password, hashed at startup |
AUTHENTICATION_SECRET_KEY |
Session signing secret |
AUTHENTICATION_API_KEY |
API access for MCP and scripts |
SHARE_PUBLIC_ORIGIN |
Public URL used for share links |
DEFAULT_THEME |
Initial theme for new browsers |
AUTOSAVE_DELAY_MS |
Editor/drawing autosave debounce |
For reverse proxy deployments, also consider Uvicorn’s FORWARDED_ALLOW_IPS=* so forwarded scheme/client headers are trusted.
Local Python Setup
For development, Docker is not required.
git clone https://github.com/gamosoft/NoteDiscovery.git
cd NoteDiscovery
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python run.py
The package metadata requires Python >=3.10.
Optional Ollama Stack
The repo also includes docker-compose.ollama-stack.yml.
That stack runs:
- NoteDiscovery on
8000. - Ollama on
11434. - Open WebUI on host
3000. - A one-shot model pull service for
qwen2.5:1.5b.
I inspected this file but did not run it. It pulls additional images and a local model, so it is a heavier test than the one-container NoteDiscovery deployment.
Use it when you specifically want a bundled local LLM environment next to your notes.
Security Notes
Authentication is disabled by default in the sample config. If the app is reachable from anything beyond your own machine, turn it on.
Use:
- A strong unique password.
- A generated session secret.
- HTTPS behind a reverse proxy.
- An API key for MCP clients instead of relying on browser sessions.
- Regular backups of
data/.
Also remember that this is a single-user style app, not a full enterprise wiki with multi-user permissions, audit trails, SSO, or compliance controls.
When NoteDiscovery Fits
NoteDiscovery is a good fit if you want:
- Plain Markdown files.
- A simple Docker deployment.
- No database.
- A browser editor and reader.
- Local search, tags, backlinks, and graph view.
- AI assistant access through MCP.
- A notes app that remains inspectable on disk.
It may be too simple if you need collaborative editing, complex permissions, enterprise identity, or a large multi-user documentation platform.
Alternatives to Know
For similar self-hosted knowledge workflows, compare:
- SilverBullet for a programmable Markdown knowledge base.
- Memos for short-form notes and micro-journaling.
- Logseq for local-first outlining and graph notes.
- Trilium Notes for a more database-backed hierarchical notes system.
NoteDiscovery stands out when you want a lightweight browser UI over a plain Markdown vault plus an MCP bridge.
Conclusion
NoteDiscovery is a practical self-hosted notes app: FastAPI backend, static frontend, plain-file storage, Docker image, optional auth, plugins, themes, exports, graph features, and MCP support.
The isolated Docker smoke test worked cleanly on port 18080: health passed, API note creation worked, search found the note, and the trial container was removed afterward.
For a homelab, the main production work is straightforward: mount ./data, enable authentication, set a strong session secret, back up the vault, and put it behind HTTPS if it leaves your private machine.
FAQ
Does NoteDiscovery need a database?
Can I use it with existing Markdown notes?
NOTES_DIR at your vault locally, or bind-mount your vault into the container and set NOTES_DIR accordingly.
Does it support AI assistants?
Is authentication enabled by default?
AUTHENTICATION_ENABLED=true, set AUTHENTICATION_PASSWORD, and set a generated AUTHENTICATION_SECRET_KEY before exposing the app to a network.
Was the Docker test isolated?
/tmp data bind mount, host port 18080, and the existing Docker bridge network. Only that trial container was stopped and removed.
Comments