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

The upstream project provides two main Compose paths:

  • docker-compose.ghcr.yml for the prebuilt GHCR image.
  • docker-compose.yml for 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