Validated against v1.1.5 on 2026-03-15.
This guide is optimized for people arriving from the blog post. If you just want to see the stunt, use the reader-safe paths first.
git clone https://github.com/flyingrobots/git-cms.git
cd git-cms
npm run setup
npm run demo
npm run sandboxIn another terminal:
npm run sandbox:shell
git -C "$GIT_CMS_REPO" for-each-ref refs/_blog/
git -C "$GIT_CMS_REPO" log refs/_blog/dev/articles/hello-world -1 --format="%B"
git -C "$GIT_CMS_REPO" log refs/_blog/dev/articles/hello-world --graph --oneline| Mode | Command | Git repo location | Intended audience |
|---|---|---|---|
| Demo | npm run demo |
Disposable isolated repo inside a temporary Docker project | First-time readers |
| Sandbox | npm run sandbox |
/data/repo in the container, backed by a named Docker volume |
Readers who want to inspect and tinker |
| Dev | npm run dev |
/app (the bind-mounted checkout) |
Contributors working on git-cms itself |
If you are here to understand the stunt, start with demo or sandbox.
npm run setupThis checks:
- Docker is installed
- Docker Compose is available
- the Docker daemon is running
npm run demoWhat the demo shows:
- create a draft
- inspect refs
- inspect the commit message
- publish by moving a ref
- make another edit
- inspect version history
The demo uses a disposable isolated repo and cleans itself up when it exits.
npm run sandboxOpen your browser to http://localhost:4638.
What just happened:
- Docker built the runtime image
- the container created or reused
/data/repo - if the repo was empty, the sandbox seeded
hello-world - the server started against
/data/repo
In another terminal:
npm run sandbox:shellThe source code lives in /app. The live Git repo lives in $GIT_CMS_REPO, which defaults to /data/repo.
Recommended inspection commands:
git -C "$GIT_CMS_REPO" for-each-ref refs/_blog/
git -C "$GIT_CMS_REPO" log refs/_blog/dev/articles/hello-world -1 --format="%B"
git -C "$GIT_CMS_REPO" log refs/_blog/dev/articles/hello-world -1 --format="tree: %T"
git -C "$GIT_CMS_REPO" log refs/_blog/dev/articles/hello-world --graph --onelineWhat you should see:
- the article body inside the commit message
- an empty-tree commit
- a published ref behind the current draft ref
- multiple restore-worthy historical commits
The sandbox deliberately starts with an interesting repo:
hello-worldpublished at v1- draft v2 and v3 ahead of published
- a history chain you can browse and restore immediately
That means the UI is alive on first load and the Git inspection commands have something real to show.
While npm run sandbox is running:
- Open http://localhost:4638
- Click
hello-world - Compare the current draft with the published state
- Open the History panel
- Preview an older version
- Restore it and watch a new commit appear
The important thing to notice is that restore does not rewrite history. It appends a new draft commit with old content.
If you are modifying the codebase itself:
npm run devThis mode is different from sandbox:
- the checkout is bind-mounted into
/app - the running app uses
/appas the Git repo - it is meant for contributor iteration, not blog-reader safety
Use dev when you are working on git-cms, not when you are just exploring how it works.
These commands keep Git activity away from the checkout:
npm run demonpm run sandboxnpm test
npm run dev is intentionally different. It uses the checkout as the runtime repo. That is useful for contributors, but it is not the right first step for article readers.
To stop services and remove volumes:
docker compose down -vThat removes the long-lived playground repo volume too.
npm test
npm run test:e2e
npm run test:sandboxWhat they cover:
- core Git and service behavior
- admin UI
- isolated seeded sandbox smoke path
The current HTTP history endpoints assume Git's default SHA-1 object format. In practice, /api/cms/show-version and /api/cms/restore accept 40-character hexadecimal commit IDs.
Assets can be encrypted server-side before they are written into Git via @git-stunts/git-cas.
git-stargate is optional hardening, not part of the first-run path. If you want a Git-native gateway with fast-forward-only and signed-push enforcement, see README.md.