FrankMD keeps your notes as markdown files on your own disk
Self-hosted, Simple Markdown Web Editor and Notes Organizer
At a glance
- What is it?
- FrankMD is a self-hosted Rails 8 note-taking app whose editor writes plain markdown files to a directory you mount, with a live preview, wikilinks and an offline-safety layer that disables editing when the server goes away. It suits a writer who wants plain files on their filesystem, and it is a single-user tool rather than a team knowledge base.
- Who is it for?
- Adopt FrankMD if you want a note-taking tool whose output is a directory of markdown files you can read with any other editor, keep in git, or move to another tool later, because that property is what the application is built around. Do not adopt it for shared or multi-user writing, since the authentication surface is a single optional token rather than accounts, and do not adopt it if you want structured data, since there is no database.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Notes as files, not rows in a table
FrankMD is a self-hosted markdown note-taking app built with Ruby on Rails 8, and the name honours Frank Rosenblatt, inventor of the Perceptron, with fed as the command-line alias. The reason the project exists is stated in four bullets. There is no database, because notes are plain markdown files on your filesystem. It is self-hosted, so your data stays on your machine or server. It is Docker-ready, with one command to start writing. And it is blog-friendly, letting you draft posts with live preview. The no-database decision is the one that shapes everything else. It means the notes directory is the database, and that directory can be a git repository, a synced folder, a mounted volume, or anything else that presents a filesystem. It also means the application has no notion of accounts, no permissions model and no multi-user concurrency, which is what makes it a personal writer's tool rather than a team knowledge base. The repository layout reflects the Rails shape, with app/, config/, lib/, bin/, script/, scripts/, test/ and public/ directories, alongside the notes/ directory that holds the actual content, a Dockerfile, a docker-compose.yml, a .kamal/ directory for deployment, and a releases/ directory.
The editor: CodeMirror, typewriter mode and optional Vim
The editing experience is built on CodeMirror 6, and the JavaScript dependencies make the feature set legible. The package lists @codemirror/state, @codemirror/view, @codemirror/commands and @codemirror/search, with the markdown language mode, and a set of @lezer grammars for C++, Go, Java, PHP, Python, Rust and YAML, which are there for fenced code blocks with syntax highlighting. The documented editor features are a clean, distraction-free writing interface, syntax highlighting for markdown, auto-save with visual feedback, typewriter mode for focused writing where the cursor stays centred, an optional Vim mode with modal editing and colon ex-commands that is off by default, customizable fonts and sizes, and multiple colour themes with light and dark variants. Two of those are worth calling out for the writing itself. Typewriter mode keeps the active line centred so your eyes do not have to travel, and it is one of the few features of the kind that genuinely changes how much you can write in a sitting. The Vim mode is the opposite trade, off by default, and for anyone who already has modal editing in their fingers it removes the friction of reaching for the mouse.
Offline detection, the 20 percent rule and the recovery diff
The data safety section is the most distinctive part of the feature list, and it is worth reading in full because it shows the author thinking about a specific failure. When the server becomes unreachable, FrankMD disables the editor and shows a warning banner to prevent edits that cannot be saved, with a Retry button to manually re-check, and the editor re-enables automatically once the connection is restored. That last detail matters, because a client that only notices the failure on the next request will happily accept text and lose it. The content loss protection is the second piece. If you accidentally delete a large portion of your note, defined as more than 20 percent and 50 or more characters, a warning banner appears with Undo and Save Anyway buttons, giving you a chance to recover before the deletion is saved. Third, an offline backup: while you are editing, FrankMD periodically saves your work to the browser's local storage as a safety net. Fourth, a recovery dialog. If the app detects that a local backup differs from the saved version, for example after a crash or a lost connection, it shows a side-by-side diff so you can choose to keep the server version or restore the backup. For a tool whose entire value proposition is that your files are just files on disk, the risk is not data loss in the usual sense but a partial write, and these four mechanisms are all aimed at that specific risk.
Finding and linking: Ctrl+P, regex search and wikilinks
The organisation features treat the notes directory as a tree you browse rather than a database you query. There is a nested folder structure with a context menu for creating notes and folders by right-click, and drag and drop for files and folders. The quick file finder is on Ctrl+P and is sorted by recency, which is the right default for a personal notes app, since the note you want is usually the one you touched last. Full-text search with regex support is on Ctrl+Shift+F, and find and replace with regex support is on Ctrl+H, which is a level of search that most markdown editors skip. Linking is done with wikilink syntax, where a double-bracketed note title links to another note, and each note lists which other notes link back to it. Backlinks are the feature that turns a folder of files into something navigable, and implementing them without a database means computing the reverse edges at read time. There is also Hugo blog post support, which creates posts with the directory structure Hugo expects, so a note can graduate into a published post without a second tool.
Preview, media tooling and the AI features
The preview panel is a live markdown view with synchronised scrolling, including in typewriter mode, plus zoom controls, GitHub-flavored markdown support and a copy button on rendered code blocks. The media tooling is unusually broad for a notes app. Images can be browsed from the local filesystem, searched on the web through DuckDuckGo, Google Images or Pinterest, or generated with AI from a configured provider. Videos can be embedded by searching YouTube or from local files. There is a visual table editor with drag and drop for rows and columns, a code block language selector with autocomplete, and quick pickers for emoji and for emoticons. The AI section covers grammar checking for grammar, spelling and typos, presented as a side-by-side diff of the original and the corrected text, with the corrections editable before you accept them, and the documentation says it supports Ollama running locally, OpenAI, Anthropic, Gemini and OpenRouter. The local Ollama option is the one that matters for a self-hosted tool, since it keeps the writing assistance inside your own machine. The interface is available in seven languages according to the documentation, and the internationalisation work is documented alongside a scripts/ directory in the repository layout.
Running it: Docker, the notes mount and the port
The deployment path is Docker Compose, and the compose file spells out the quick start in its own comments.
services:
frankmd:
image: akitaonrails/frankmd:latest
container_name: frankmd
restart: unless-stopped
user: "${UID:-1000}:${GID:-1000}"
ports:
- "${FRANKMD_BIND_ADDRESS:-0.0.0.0}:${PORT:-7591}:80"
volumes:
- ${NOTES_PATH:-./notes}:/rails/notes
- ${IMAGES_PATH:-./images}:/rails/imagesTwo of those lines deserve attention. The container runs as the host user rather than root, using the UID and GID environment variables, which is what makes the bind-mounted notes directory writable without a permission fight. And the published port defaults to all interfaces, with the compose file commenting that you can set FRANKMD_BIND_ADDRESS to 127.0.0.1 for local-only access, which is worth doing unless you intend to reach it from another machine. The .env.example file spells out the rest of the configuration, with a required SECRET_KEY_BASE generated by openssl rand -hex 64, the notes and images paths, the port defaulting to 7591, the bind address, the UID and GID for Docker user mapping, an optional FRANKMD_AUTH_TOKEN of at least 32 bytes generated with openssl rand -hex 32, and an optional FRANKMD_ALLOWED_HOSTS list of hostnames and addresses. There are also optional keys for AWS S3 image uploads, a YouTube API key and other provider settings.
Where FrankMD does not fit
The boundaries follow from the design rather than from missing features. Because there is no database and no account system, this is a single-user tool, and the access control is a shared token plus a host allowlist rather than accounts with roles. That is fine for a laptop, and it is a real constraint the moment a second person needs access. The second boundary is the local-first assumption. A note that lives only on your filesystem cannot be edited from your phone unless you sync that filesystem, so the mobile story depends on your own sync setup rather than on the app. The third is the AI surface. The grammar checker, the AI image generation and the YouTube and image search integrations all reach out to third-party services, so the privacy claim holds for your notes but not for the requests you make while using those features, unless you point the model at a local Ollama instance. The last point is about the project itself. The licence is recorded as unknown, the repository has no LICENSE file at its root in the listing, and the releases are frequent, with v1.7.0, v1.8.0 and v1.9.0 published on three consecutive days in October 2026. That is a fast-moving project, and a team that depends on it should decide early whether it is a dependency or a starting point to fork.
Editorial conclusion
Adopt FrankMD if you want a note-taking tool whose output is a directory of markdown files you can read with any other editor, keep in git, or move to another tool later, because that property is what the application is built around. Do not adopt it for shared or multi-user writing, since the authentication surface is a single optional token rather than accounts, and do not adopt it if you want structured data, since there is no database. Verify two things before you deploy it anywhere but your own machine: that FRANKMD_BIND_ADDRESS is set to 127.0.0.1, because the default publishes the port on all interfaces, and that FRANKMD_AUTH_TOKEN is set, because the .env documentation says to leave it unset only for trusted-LAN deployments. The practical first step is to run docker compose up -d with a NOTES_PATH pointing at a directory you already care about, then confirm the notes appear as files on disk rather than only in the browser.
Frequently asked questions
What is FrankMD?
It is a self-hosted markdown note-taking app built with Ruby on Rails 8, with fed as its command-line alias. Notes are plain markdown files on your filesystem, with no database, and the name honours Frank Rosenblatt, inventor of the Perceptron.
How do I run FrankMD with Docker?
The compose file uses the image akitaonrails/frankmd:latest, runs the container as your host user via the UID and GID variables, mounts the notes and images directories, and publishes the port with FRANKMD_BIND_ADDRESS and PORT. The compose comments give docker compose up -d as the quick start.
What happens to my editing if the FrankMD server goes offline?
The editor is disabled and a warning banner appears so you cannot make edits that cannot be saved, with a Retry button to re-check manually, and the editor re-enables automatically once the connection is restored. Work in progress is also periodically saved to the browser's local storage as a safety net.
Does FrankMD support wikilinks and backlinks?
Yes. A note links to another with the double-bracketed title syntax, and each note lists which other notes link back to it. The documentation also covers a quick file finder on Ctrl+P, full-text search with regex support on Ctrl+Shift+F, and find and replace with regex support on Ctrl+H.
Which AI providers does FrankMD support?
The grammar check feature supports Ollama running locally, OpenAI, Anthropic, Gemini and OpenRouter, and the image generation feature requires a configured AI provider. The corrected text is shown in a side-by-side diff and the corrections can be edited before you accept them.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/akitaonrails-frankmd)Was this useful?