A stateless blog engine in Go

Write in the browser.
Ship as a commit.

trunkcms keeps your whole site in a GitHub repo. Editors sign in with GitHub and write Markdown at /admin. Every save is a commit, and every instance rebuilds and swaps in the new site within seconds.

Get started
main live
  1. save posts/hello-world.md
  2. commit 3f9a2c1 Publish hello-world
  3. build 0.2s · 0 errors
  4. swap live everywhere ✓

How a save goes live

Your repo is the database.
Every instance is a cache.

  1. 01

    Write

    Markdown in a plain textarea, with a live preview rendered by your real theme. Images are staged into the same save.

    POST /admin/save
  2. 02

    Commit

    One atomic, multi-file commit through the Git Data API, authored as you. If someone else got there first, you're told before anything is lost.

    PATCH refs/heads/main
  3. 03

    Rebuild

    A push webhook, or a poll that costs nothing when nothing changed, wakes every replica. Each one streams the tarball and renders the whole site.

    GET /tarball/{sha}
  4. 04

    Swap

    An atomic pointer swap puts the new site live with zero downtime. If a build fails, the last good site keeps serving.

    atomic.Pointer.Store

Not in the box

  • Database
  • Object storage
  • Queue
  • Redis
  • Node build step
  • Redeploy per post

GitHub is the only external service.

The container image is the engine and nothing else. Content changes never rebuild the image. They arrive as commits, and the running process re-renders itself in place. The same binary runs on Kubernetes, Cloud Run, ECS / Fargate, or Lambda.

  • Kubernetes
  • Cloud Run
  • ECS / Fargate
  • Lambda

What you get

Small engine. Sharp edges filed off.

Drafts at their real URL

draft: true keeps a post out of lists and feeds. Signed-in editors see it at its real address with a banner. Everyone else gets a plain 404.

Scheduling without cron

Give a post a future date and it publishes itself. The sync loop rebuilds the moment the next one comes due.

Roles from GitHub

Push access makes you an admin. Authors and editors are listed in users.yaml and never need access to the repo.

Edit however you like

git push, a merged PR, or GitHub's web editor all fire the same webhook. The admin UI is just one convenient way to commit.

Themes are just files

Drop a theme into themes/, override a single template in theme/. Switching is test-built before it's committed. This site's theme sits right next to its posts.

The boring parts, done

RSS and Atom, sitemap, tag and author pages, footnotes, and syntax highlighting are all built in. Pages are content-addressed and served with ETags.

Footprint, measured

Rebuilding everything is cheap.

0.8sfull rebuild,
1,000 posts
~7MBmemory,
1,000 posts
3.8sfull rebuild,
5,000 posts
1external
service

Measured with 1,000-word posts. Images cost disk, not memory. Reproduce it with TRUNKCMS_MEMTEST=1 go test -run MemoryFootprint ./internal/build/

Try it

Thirty seconds, no GitHub needed.

Local mode serves and edits a plain directory. Sign in as dev-admin, dev-editor, or dev-author to try each role.

Full setup guide
~/src
# grab the engine
$ git clone https://github.com/nullism/trunkcms
$ cd trunkcms

# serve the example site, then open /admin
$ TRUNKCMS_CONTENT_DIR=./testdata/site \
    go run ./cmd/trunkcms
listening on :8080

Journal

Recent commits

  1. · @nullism

    How a save becomes a deploy

    Following one click of the Save button through the Git Data API, a webhook, a tarball, and an atomic pointer swap.

    internalsgit

  2. · @nullism

    Introducing trunkcms

    A blog engine where the Git repo is the database, every save is a commit, and GitHub is the only thing it talks to.

    releasemeta