Status: early development. This document describes the intended design of the first version; parts of it are not implemented yet.

To set up gramr, see Getting started. For every command and setting, see the reference: commands, configuration, front matter, errors.

What is gramr?

gramr is a command-line tool that publishes your notes as static websites.

You write Markdown wherever you like: an Obsidian vault, a Logseq graph, or a plain folder of .md files. You mark the notes you want to publish in their front matter, and gramr publish <site> takes care of the rest. It selects those notes, injects them into the site's repository, builds a Docker image and pushes it to your container registry.

your notes  ──►  gramr  ──►  container image in your registry
(any folder)       │
                   └── site repository (theme, templates, Dockerfile)

One tool, several sites, one workflow, with a small configuration file per site.

What gramr does

  • Selects notes from a source folder, based on their front matter. Notes can live anywhere in the source folder, so you don't have to move or copy anything.
  • Places each note in a section of the site, independently of where it lives in your source folder.
  • Rewrites links between notes ([[wikilinks]], embeds) into links your static site generator understands.
  • Protects private content: links to unpublished notes are neutralised, comments are removed, and only the attachments (images, files) actually referenced by published notes are copied.
  • Publishes unlisted pages: pages reachable only by a secret URL, hidden from listings, feeds, the sitemap and search.
  • Assembles a temporary workspace from the site repository (a local folder, or a Git ref of a remote repository) plus the selected content.
  • Builds a Docker image using the Dockerfile of the site repository, for the target platforms (for example linux/arm64 for a Raspberry Pi).
  • Versions the site: every published state gets an immutable version, computed from everything that goes into the image. Changing a note or the style of the site gives a new version; rolling back means running an older one.
  • Tags and pushes the image to an OCI registry (Forgejo, Gitea, GHCR, Docker Hub…), with the version, latest, the Git commit when known, and OCI labels.
  • Shows exactly what would be published with --dry-run, before anything is built.
  • Previews the site locally with gramr preview <site>: the same steps, then the image runs on your machine until Ctrl+C.

What gramr does not do

  • It does not deploy. gramr stops once the image is pushed. Pulling and running it (Docker Compose, Watchtower, a registry webhook, Kubernetes…) is up to you.
  • It does not render your site. The static site generator (Zola, Hugo, Astro…) runs inside your Dockerfile. gramr never calls it directly and does not need it installed.
  • It does not define the look of your site. Themes, templates, sections and listing pages live in the site repository.
  • It does not manage secrets. Registry authentication relies on your existing docker login, and Git access on your existing SSH or credential setup.
  • It does not push your notes to Git. Your source folder stays private and is never committed to the site repository; content only exists in the temporary workspace and in the built image.
  • It does not provide access control. Unlisted pages are hidden behind hard-to-guess URLs, not behind authentication. Do not use them for confidential content.

Philosophy

Agnostic where it doesn't matter

  • Any editor. gramr reads Markdown files with YAML front matter. Obsidian-style [[wikilinks]] and ![[embeds]] are supported; plain Markdown works as is.
  • Any static site generator. The Dockerfile is the contract: if your site builds in Docker, gramr can publish it. The only generator-specific setting is which generator you use, so gramr knows how to write links and hide unlisted pages.
  • Any registry, any host. The deliverable is a standard OCI image.

Opinionated where it does

  • Publishing is opt-in, note by note. Nothing is published unless a note explicitly says so. A forgotten flag means a page missing from the site, never a private note leaking.
  • When in doubt, fail. An invalid setting, an ambiguous link or a mistyped key stops the pipeline with a clear message, rather than publishing something you did not mean to.
  • Content and presentation are separate. Your notes hold the content. The site repository holds the presentation (theme, templates, sections) and the build recipe (Dockerfile, gramr.toml). Neither knows how the other is organised.
  • A note decides where it goes. Its place on the site comes from its front matter, not from the folder it lives in. Organise your notes for yourself, and the site for your readers.
  • gramr names files, your generator builds URLs. gramr decides where each page is written and links pages by file. Permalinks and routes stay the generator's business.
  • The image is the boundary. gramr's job ends with a versioned, reproducible image in a registry. Everything after that belongs to your infrastructure.
  • Reuse existing tools. Git and Docker do the heavy lifting; gramr orchestrates them instead of reimplementing them.
  • Reproducible builds. Generator versions are pinned in the Dockerfile, and the same notes and site repository always give the same version.
  • Git is optional. It is only used to fetch a remote site repository. Versioning does not depend on it, so a local folder works too.

How it works

gramr brings together three things:

  • Your notes, in a source folder. A note opts in with a few keys in its front matter:
    ---
    gramr:
      publish: public
      site: blog
      section: articles/rust
    title: Understanding lifetimes
    ---
  • A site configuration, one small file per site in ~/.config/gramr/sites/: where the site repository is, which source folder to read, and where to push the image.
  • The site repository: theme, templates, sections, a Dockerfile, and a gramr.toml describing how the site is built. gramr init creates the last two for you.

gramr publish <site> then runs these steps:

  1. Load configuration: global config, site config and the site repository's gramr.toml.
  2. Prepare the workspace: copy the local site folder, or clone the Git ref of the remote repository, into a temporary folder.
  3. Select notes: walk the source folder and keep the notes whose front matter targets this site.
  4. Transform content: rewrite links, neutralise links to unpublished notes, remove comments, collect referenced attachments.
  5. Check and write: every target section exists, no two pages go to the same file, nothing overwrites a file of the site. Any problem stops the pipeline. The content is then written into the workspace, and the version computed.
  6. Build and push: run docker buildx build --push on the workspace.
  7. Clean up and report: remove the workspace (unless --keep-workspace) and print the published tags.

With --dry-run, gramr stops after step 5 and prints the pages, attachments, neutralised links and image tags.

gramr preview <site> runs the same steps, but builds the image locally instead of pushing it, and serves it at http://localhost:8080 until Ctrl+C.

Roadmap

First version

  • Public and unlisted pages, one or more sites.
  • Obsidian-style links and embeds, Zola.
  • Docker build and push, --dry-run, --keep-workspace.

Later

  • Leak check: scan the build output for unlisted tokens before pushing.
  • Skipping the push when the version already exists in the registry.
  • gramr build, gramr site add, gramr site edit.
  • More generators (hugo, astro), Logseq property syntax.
  • Several source folders for one site (paths = [...]).