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.
A stateless blog engine in Go
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.
How a save goes live
Markdown in a plain textarea, with a live preview rendered by your real theme. Images are staged into the same save.
POST /admin/save
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
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}
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
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.
What you get
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.
Give a post a future date and it publishes itself. The sync loop rebuilds the moment the next one comes due.
Push access makes you an admin. Authors and editors are listed in users.yaml and never need access to the repo.
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.
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.
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
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
Local mode serves and edits a plain directory. Sign in as dev-admin, dev-editor, or dev-author to try each role.
# 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
Following one click of the Save button through the Git Data API, a webhook, a tarball, and an atomic pointer swap.
A blog engine where the Git repo is the database, every save is a commit, and GitHub is the only thing it talks to.