files.md is a folder you can open, plus one Go binary, with the last README section cut off mid-word
🌱 Private, quiet space for thinking. Simple app for .md files.
At a glance
- What is it?
- The files.md project is a local-first Markdown notebook that ships as a static web app you can open from disk and, optionally, as a single Go binary with a Telegram bot attached. What is documented in the repository is uneven: the compose file leaves HTTPS and the sync API commented out, the Makefile's end-to-end targets begin with a machine-wide killall, and half of the README is an essay about reading practice that ends mid-sentence.
- Who is it for?
- files.md fits a reader who wants notes to stay as plain files they can open with anything else, and the two deployment routes are honest about their cost: a static folder you can open directly, or a single binary with storage and token volumes.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- zakirullin/files.md4,186 ★ · +31
| Repository | 2026-09-23 | 2026-11-14 |
|---|---|---|
| zakirullin/files.md | 4.2k | 4.2k |
31 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
No build step: web/index.html is the application
The claim that shapes everything else is portability: no build systems, no Electron, just open `web/index.html`. The same assets are what the hosted beta at app.files.md serves, and what the self-hosted image carries, since the Dockerfile copies the `web` directory into the runtime image next to the server binary. One set of files therefore covers the double-click case, the hosted case and the container case.
The update story follows from that. The install flow is to open app.files.md and use the Install files.md action in the address bar, and the documentation then tells you to hit a force refresh with `Cmd+Shift+R` occasionally to pick up new updates. There is no version pinning and no release channel in the browser; a refresh is the upgrade. The repository publishes no GitHub releases either, so the app's version is whatever the last push left on the server. Two readmes sit at the root, with the English one linking to `README.zh-CN.md`, and the last push is dated 2026-09-02.
Granting a local folder is the persistence step, and Chromium is the stated best case
Nothing is stored until you open a local folder for it. The instructions are short because the mechanism is the browser's File System Access API: pick a directory, and the app reads and writes Markdown files there. That is also where the privacy claim comes from. The project states that your data stays at your device, and the mechanism backs the statement rather than merely asserting it.
The cost is browser dependence, and the project says so in a postscript rather than hiding it: Chromium-based browsers, naming Chrome and Brave, are the best at File System Access API support, with a link to the compatibility table and an open issue for Brave. On a browser without solid support, the folder step is the part that degrades, and the app has no server-side fallback for it in the documentation. The other half of the local-first claim is that it works offline, which follows from the same design: once the folder is granted, the files are the database.
One static binary, vendored modules, and a Telegram client pinned to 2022
The server side is a single Go module, `github.com/zakirullin/files.md`, requiring Go 1.24, and the dependency list is short enough to read in one pass: `godotenv` for env files, `kelseyhightower/envconfig` for configuration from the environment, `robfig/cron` for scheduling, `spf13/afero` as a filesystem abstraction, `stretchr/testify` for tests, the `golang.org/x` crypto, text and exp modules, and `rivo/uniseg` for grapheme segmentation, which is a sensible thing for an app that stores prose. The Telegram client is pinned to `v5.5.2-0.20221020003552-4126fa611266`, a pseudo-version from October 2022, while several `golang.org/x` modules sit at mid-2024 releases.
The build is a two-stage Dockerfile: a `golang:1.24-alpine` builder runs `go mod download`, then builds `./cmd/server` with `CGO_ENABLED=0 GOOS=linux` and stamps the version in through `-X main.Version` from a build argument that defaults to `dev`. The runtime is `alpine:3.20` with ca-certificates and tzdata added, a non-root `app` user at uid 1000, and two volumes declared for storage and tokens. A `vendor/` directory sits at the top level, so the tree carries its dependencies and a build needs no module fetch.
compose.yaml publishes port 80 with HTTPS and the sync API commented out
The shipped compose file is the fastest way to read the project's defaults. It builds from the Dockerfile, names the image `files-md`, and maps `80:8080` with the `443:443` line sitting commented out directly underneath. The environment block sets `APP_URL=http://localhost`, `STORAGE_DIR` and `TOKENS_DIR` to the mounted volume paths, and `CERT_DIR: ""` with a comment saying to provide a directory such as `/opt/certs` if you want HTTPS enabled. A second line, `API_URL`, is commented out with the note that you should uncomment it if you want the synchronization API.
So out of the box you get plain HTTP on port 80, no certificates, and no synchronization endpoint. Two named volumes, `storage` and `tokens`, persist across `compose down`. The tokens volume is the piece to think about: the Makefile's environment block carries a `BOT_API_TOKEN` entry, which means the Telegram bot credential is a file on a mounted volume rather than something held in memory, and anyone self-hosting this should decide where that volume lives before putting the container on a reachable host.
The e2e targets start with killall server, and .PHONY names five of eighteen
The Makefile is where the sharp edges are. Every end-to-end target begins the same way, with `killall server || true` before starting a fresh process, and the plain `e2e` target also redirects the server log to `/tmp/server_err`. That command is not scoped to this project: it matches any process called `server` on the machine, so running the browser tests on a workstation that happens to run something else under that name takes that process down too.
The other problem is declaration. The `.PHONY` line lists `server docker_build docker_run compose_up compose_down` and stops, while the file goes on to define `test`, `check`, `lint`, `format`, `e2e`, `e2eh`, `e2es`, `e2esh`, `perf`, `sync`, `synch` and `report`. Those are ordinary file targets as far as make is concerned, so they run only when no file of the same name exists in the working directory. The rest of the file is careful: `DOCKER ?= docker` so the container targets can be pointed at podman, `check` runs fmt, vet and tests together, and the e2e targets drive a Playwright suite from the `tests/` directory with a headed variant and a `perf` target that sets `PERF=1`.
Synchronization is optional, and the Telegram bot is the documented alternative
Sync is presented as a choice rather than a feature. The compose file leaves the API endpoint commented out, and the README offers two other routes: run the single binary and use iCloud, Dropbox or Google Drive for sync, or rely on the built-in synchronization when you uncomment the API line. The test suite has dedicated targets for it, `sync` and `synch`, which run the same browser tests filtered by the word sync, so the path is exercised even when it is off by default.
For capture on the move there is a Telegram bot, and the README links it directly while promising that other messengers will follow. That promise is the only forward-looking statement in the file. What is concrete is the shape: a bot token in the environment, a `tokens` volume to hold it, a `robfig/cron` dependency in the server for scheduled work, and a chat surface in the app itself, opened with `Cmd+Enter`, where a message is sent and then assigned to a folder, with the option to choose the destination later rather than at capture time.
Half the README argues about note taking, and the last section stops after one word
The documentation is two documents wearing one filename. The first half is usable: how to install, how to grant a folder, how to capture a thought in the chat, what the bot does. The second half is an essay about reading practice, written in the first person, including a section on thinking deeply that recommends one idea per note, notes that make sense without context, and moving them into `brain` or `dev` folders, with a link to an article the author wrote after noticing a connection between the two.
There is a long quoted passage from an essay called I Deleted My Second Brain, retrieved through a web archive link, about how note systems turn into a way of deferring thought to a future self who never arrives, and a following argument that notes can prevent experience because knowing something well enough feels like having done it. The final section, on self-help through reading and note taking, opens and stops after its first bolded word, so the argument the file builds toward is not written down here. Alongside that sit two project facts worth keeping: the author states they have been building this for five years and asks for GitHub Sponsors support, and the sizing claim is that one person, or an LLM, can hold the whole project in their head.
Editorial conclusion
files.md fits a reader who wants notes to stay as plain files they can open with anything else, and the two deployment routes are honest about their cost: a static folder you can open directly, or a single binary with storage and token volumes. Check the parts the documentation leaves to you before putting it on a public host. `compose.yaml` publishes port 80 with the HTTPS line commented out and `CERT_DIR` empty, so certificates are your problem; `BOT_API_TOKEN` lands in a mounted volume; and the app is a progressive web app you force-refresh to update, which is fine for notes and not what you want from a service holding real data. For self-hosted use, set `CERT_DIR`, decide whether synchronization is actually needed, and expect the browser side to depend on Chromium's File System Access API.
Frequently asked questions
What is a MD file?
The project treats a Markdown file as plain text you own, and that is the whole premise: notes, documents and projects, a journal and habits, checklists and tasks all live in `.md` files on your own disk. Its stated principle is to own your data as plain local files and to own the software that opens them, on the grounds that local files and owned software last.
What will open a .md file?
For the browser, this project's own app: a static bundle you can open directly as `web/index.html` with no build system and no Electron, or the hosted `app.files.md`, which installs from the address bar and then persists to a local folder through the browser's File System Access API. Chromium-based browsers such as Chrome and Brave are named as the best case for that API.
Can we open a .md file in Chrome?
Chrome is the case the project optimises for, described as one of the Chromium-based browsers that are best at File System Access API support. The app is static files, and the self-hosted image copies the `web` directory next to the server binary, so the same assets work when you serve them yourself.
Can I open a .md file in Word?
That question is not addressed anywhere in the repository. The documentation only speaks about browsers, granting a local folder, plain files, and using iCloud, Dropbox or Google Drive as sync alternatives, so what a word processor does with the format is outside what this project claims to answer.
Does files.md need a server to run?
No, and that is the point: the web app is static and can be opened from disk. If you want the server side anyway, the Dockerfile builds `./cmd/server` into a single static binary with CGO disabled and runs it on alpine as a non-root user, with compose publishing port 80 and mounting `storage` and `tokens` volumes.
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/zakirullin-files-md)Star history chart for your README
[](https://hysenlabs.com/projects/zakirullin-files-md)Was this useful?