Documentation

pulpme.sh publishes a folder of Markdown files as a blog. You upload the folder over SSH with scp or sftp, and the site is rebuilt and served at pulpme.sh/~NAME. This page covers names, uploads, blog settings, posts, the supported Markdown, and limits.

Quick start #

  1. Create a folder with a _pulpmesh.md file and at least one post, for example hello-world.md.
  2. Upload the folder, replacing NAME with the name you want:
    scp -r ./notes NAME@pulpme.sh:/
  3. Open https://pulpme.sh/~NAME.

The SSH key used for the first upload becomes the only key that can publish under that name. See Names.

Names #

The SSH username is the blog name. There is no sign-up step.

Claiming a name

The first connection to a name that nobody holds claims it and records the public key that connected. This applies to any connection, including a plain ssh NAME@pulpme.sh, so check the spelling before you connect.

After that, only the recorded key is accepted for the name. A different key gets Permission denied (publickey).

Important There is no account recovery. If you lose the key, you lose access to the name.

Name rules

  • 1 to 32 characters.
  • Lowercase letters a-z, digits 0-9 and hyphens.
  • Must start and end with a letter or a digit.

The following names are reserved and cannot be claimed: about, admin, api, assets, cdn, docs, ftp, help, mail, pulpme, pulpmesh, root, ssh, static, status, support, www. Connecting with a reserved or invalid name prints the reason and disconnects.

Publishing #

Uploading

Upload the folder recursively to /:

scp -r ./notes NAME@pulpme.sh:/

Both the SFTP-based scp of OpenSSH 9.0 and later and the legacy protocol (scp -O) are supported, as is sftp. If the upload contains a single top-level folder, its contents are used as the blog. Uploading ./notes and uploading the files inside it give the same result.

The server only accepts uploads. Listing, downloading, renaming and deleting files are not supported.

Replacement

Each upload replaces the blog's source completely. Files that are not in the upload are removed from the site. Always upload the full folder, not only the files you changed.

The new version goes live only after the upload finishes and the build succeeds. If the upload is interrupted, a file is rejected, or the build fails, the previous version stays online unchanged.

If the uploaded files are identical to the published ones, no build runs.

Checking the last build

ssh NAME@pulpme.sh

This prints the date the name was claimed, the blog's address, and the result of the last build with any warnings from the renderer. Use it when a publish does not appear: scp closes the connection before it can display a build error.

Messages

MessageCause
Permission denied (publickey)The name is held by a different key.
… is a name this service keeps for itselfThe name is reserved.
… can't be a blog nameThe name breaks the name rules.
… is not a Markdown fileThe upload contains a file other than .md or _styles.css.
… over the 33 KiB a single file can beA file exceeds the per-file limit.
too many filesThe upload has more than 500 files.
too many publishes in a rowThe build rate limit was reached. Wait and upload again.
your files match what is already publishedNothing changed, so no build ran.
your blog didn't buildThe renderer failed. The output that follows gives the reason.
only file uploads are supportedThe connection ran a command or file operation other than an upload.

Files #

notes/ _pulpmesh.md blog settings and intro hello-world.md post _half-baked.md draft, not published _styles.css custom CSS, optional
FilePurpose
_pulpmesh.mdBlog settings and index page intro. Optional. See Blog settings.
*.mdA post.
_*.mdA draft. Stored with the upload, never rendered.
_styles.cssCustom stylesheet. Optional. See Styles.

Any other file type is rejected and the upload stops at that file.

Only the top level of the folder is read. Files in subdirectories are accepted but never published.

Blog settings #

The front matter of _pulpmesh.md holds the blog settings. The body after the front matter is rendered as the intro at the top of the index page. All keys are optional, and a blog without _pulpmesh.md still builds.

Keys

KeyDescriptionDefault
titleBlog name, shown in the header, the page title, and the list on the pulpme.sh front page.src
descriptionLine shown under the title. Also used as the meta description.none
keywordsMeta keywords.blog, blogging, write, writing, hackers, developers, terminal
footerOne line of Markdown shown at the bottom of every page.pulpmesh credit line
faviconURL of the site icon. Must point to another host, because images cannot be uploaded.renderer's icon
card_imageAbsolute URL of a 300×300 image shown when the blog is linked on social sites.none
with_stylesSet to false to build without _styles.css.true

Set title. Without it the blog header shows src.

Example

---
title: Ada's notebook
description: reading notes, mostly
footer: "[mastodon](https://example.com/@ada)"
---

Notes on what I read, posted when
I get around to it.

Front page listing

The pulpme.sh front page lists recently published blogs by title. The title is taken from title, or from the first heading in the body of _pulpmesh.md if title is not set. The blog's own header only uses title.

RSS feed

Every build writes a feed to pulpme.sh/~NAME/rss.xml. No setting is required.

Posts #

Front matter

KeyFormatDescription
date2026-06-20Publish date. Values in any other format are ignored.
titletextPost title. Used only when the post has no # heading.
descriptiontextMeta description, quoted by social sites. Not shown on the page.
tags[ruby, static-site]Shown at the end of the post and in the feed. Tags are not links.

All keys are optional.

Example

---
date: 2026-06-20
tags: [ruby, static-site]
---

# Frontier model

Body text.

Title

The title is taken from the first source that exists:

  1. The first # heading. It is removed from the body so it is not shown twice.
  2. The title key.
  3. The filename.

Date

The date is taken from the first source that exists:

  1. The date key.
  2. A YYYY-MM-DD- prefix on the filename, as in 2026-06-20-frontier-model.md.
  3. The time of the upload.
Note File timestamps are not preserved, including with scp -p. A post without a date key or a date prefix is re-dated on every upload. The "last updated" line on each post always shows the date of the most recent upload.

URLs

A post is published at pulpme.sh/~NAME/ followed by its filename with .html in place of .md.

FileURL path
hello-world.mdhello-world.html
0010-frontier-model.mdfrontier-model.html
2026-06-20-frontier-model.mdfrontier-model.html
my first post.mdmy-first-post.html
_half-baked.mdnot published (draft)
  • A numeric prefix such as 0010- or a date prefix such as 2026-06-20- is removed. Use it to order files locally.
  • Characters that need escaping in a URL are replaced with -. Non-ASCII letters are kept.
  • If two files map to the same URL, the build fails and the previous version stays online.

Markdown #

Supported syntax

FeatureSupportedNotes
Headingsyes# Title or a line underlined with ===.
ListsyesTight lists only. A blank line ends the list.
Fenced codeyesSee Syntax highlighting.
Inline linksyesSee Links.
ImagesyesSee Images.
Line breaksyesA newline inside a paragraph is a line break.
Reference linksnoRendered as plain paragraph text.
TablesnoRendered as plain paragraph text.
FootnotesnoRendered as plain paragraph text.
Raw HTMLnoEscaped and shown as text.

HTML and entities

HTML is not interpreted. <b>bold</b> and <script> are shown on the page as typed. Entity references such as &copy; are also shown as typed, so write the character itself.

Allowed link destinations are http:, https:, mailto:, relative paths, and anchors. A link with any other scheme is not created, and the source text is shown instead.

Typography

InputOutput
"text", 'text'“text”, ‘text’
--– (en dash)
---— (em dash)
...… (ellipsis)

These replacements are not applied inside code, URLs, or image alt text.

Syntax highlighting

LanguageFence tags
Shellsh, bash, shell, zsh, console
Rubyruby, rb

Code blocks with any other tag, or none, are rendered as plain text without a warning.

Styles #

Custom CSS

Every page links three stylesheets in this order:

  1. The base styles.
  2. The syntax highlighting theme.
  3. Your _styles.css, if present.

Because _styles.css is loaded last, its rules take precedence. To keep the file in the upload but build without it, set with_styles: false in _pulpmesh.md.

Images

Images cannot be uploaded. ![alt](pic.png) only displays if the path resolves to an image hosted elsewhere, so use an absolute URL.

Limits #

LimitValue
File size33 KiB per file
Upload size25 MiB per upload
File count500 files per upload
Build rate3 builds in a row, then one every 10 seconds
Build time10 seconds

An upload that exceeds a size or count limit stops at the file that exceeded it, and the error names that file. A build that exceeds the time limit fails. In both cases the previous version stays online.

Uploads with no changes do not count toward the build rate.