Sign inSign up

dettmering/md2pdf

By dettmering

Updated 25 days ago

Markdown to PDF converter in the style of Obsidian Markdown export.

Image
0

2.5K

dettmering/md2pdf repository overview

md2pdf

A Docker image that turns Markdown into a clean, print-ready, Obsidian-style PDF. Two modes from one image:

  • CLI (default) — one file in, PDF out. Ideal for CI/CD.
  • Microservice (serve) — web UI + HTTP API with options for font, zoom, line width and orientation.

Both share the same pipeline: Markdown → HTML (markdown-it) → PDF via Chromium (page.pdf(), A4, printBackground). Custom callouts with a raw SVG icon work exactly like the built-in ones.

Build

docker build -t md2pdf .

CLI

Mount your working directory, file in, PDF out:

docker run --rm -v "$PWD:/data" md2pdf note.md
# -> note.pdf

docker run --rm -v "$PWD:/data" md2pdf note.md output.pdf

Paths are relative to the mounted /data.

Options
OptionShortEnvDefaultValues
--font-fMD2PDF_FONTarialarial (Liberation Sans), inter, plex (IBM Plex Sans)
--zoom-zMD2PDF_ZOOM7510200 (percent; lower = more content per page)
--measure-mMD2PDF_MEASUREreadablereadable (~80 chars, centered reading column), full (full width)
--orientation-l / --landscapeMD2PDF_ORIENTATIONportraitportrait (A4 tall), landscape (A4 wide)

The matching monospace font is selected automatically for the chosen font.

docker run --rm -v "$PWD:/data" md2pdf note.md --font inter --zoom 90 --landscape
Optional shell alias
md2pdf() { docker run --rm -v "$PWD:/data" -v "$HOME/.md2pdf-snippets:/snippets" md2pdf "$@"; }
# then:  md2pdf note.md --font inter --zoom 90

Microservice (web UI + API)

The same image starts an HTTP service with serve (default port 8080), serving both a web UI and an API. A Chromium instance stays warm (no cold start per request).

docker compose up -d --build      # -> http://localhost:8080
# or without Compose:
docker run --rm -p 8080:8080 md2pdf serve

In the web UI, drag one or more .md files onto the drop zone — each is converted and downloaded immediately. Font, zoom, line width and orientation are selectable.

API

POST /convert returns application/pdf. Options font, zoom, measure, orientation work as in the CLI. Markdown can be sent raw, as a file upload, or as JSON:

# Raw Markdown (options as query parameters)
curl -X POST "http://localhost:8080/convert?font=inter&zoom=90&orientation=landscape" \
  --data-binary @note.md -H "Content-Type: text/markdown" -o note.pdf

# File upload (multipart)
curl -X POST http://localhost:8080/convert \
  -F [email protected] -F font=arial -F zoom=75 -o note.pdf

# JSON
curl -X POST http://localhost:8080/convert \
  -H "Content-Type: application/json" \
  -d '{"markdown":"# Hello","font":"inter","zoom":100}' -o note.pdf

Errors (missing Markdown, invalid option, file too large) come back as JSON { "error": "…" } with an appropriate HTTP status.

RoutePurpose
GET /Web UI
POST /convertMarkdown → PDF (multipart, JSON or raw text)
GET /healthzLiveness check
Environment variables
VariablePurposeDefault
PORTHTTP port8080
MD2PDF_MAX_BYTESmax upload/body size (bytes)5242880 (5 MB)
MD2PDF_DEFAULT_FONTdefault font (arial|inter|plex)arial
MD2PDF_DEFAULT_ZOOMdefault zoom (10–200)75
MD2PDF_DEFAULT_MEASUREdefault line width (readable|full)readable

The DEFAULT_* variables only set the starting values; every option stays overridable per request.

Callouts

Callouts use the GitHub alert syntax:

> <p data-quote-type="tip">Tip</p> Title
> Callout content.

Custom types with their color and icon come from CSS snippets. Example snippets (including AI callouts) are baked into the image; mount your own folder to /snippets — mounted files override the baked-in ones:

docker run --rm -v "$PWD:/data" -v "$HOME/my-snippets:/snippets" md2pdf note.md
.callout[data-callout="key"] {
  --callout-color: 222, 115, 86;         /* R, G, B */
  --callout-icon: 'lucide-sparkles';     /* Lucide name OR raw SVG */
}

Supported

  • Standard Markdown (close to GFM): tables, lists, blockquotes, raw HTML
  • Callout/admonition blocks incl. custom types and icons (raw SVG or Lucide)
  • YAML frontmatter is stripped (not rendered as text)
  • PDF bookmarks (outline) generated from the headings
  • Footer with document title (frontmatter title: or first #) and page number
  • Typographic quotation marks (smart quotes; code left untouched)
  • Wikilinks [[Target]], [[Target|Alias]] rendered as text links
  • Footnotes [^1] as a numbered list at the end of the document
  • Highlight ==text== as <mark>
  • Comments %%…%% (inline and block) are removed
  • Code syntax highlighting (Prism)

Not included: Mermaid, LaTeX math, and resolving embedded file contents.

Changing the layout

Zoom, line width and orientation are controllable via options. Paper format and margins live in src/render.js (ORIENTATIONS, PAGE, format: "A4"); rebuild after changes.

Tag summary

Content type

Image

Digest

sha256:d9b4bc87e

Size

243 MB

Last updated

25 days ago

docker pull dettmering/md2pdf