Kalmia: a Go and web front end for managing RsPress documentation versions
A documentation CMS written in go
At a glance
- What is it?
- Kalmia is an AGPL-3.0 documentation CMS written in Go with a browser interface, aimed at RsPress sites, with support for multiple documentation versions, multiple users and an in-browser markdown editor, and it ships a single binary plus a Docker image with PostgreSQL and MinIO. It suits a team publishing versioned docs, and the project warns that it is still in development with breaking changes expected.
- Who is it for?
- Kalmia is worth evaluating if you publish versioned documentation on RsPress, because a documentation set with several versions and several editors is exactly the case where a folder of markdown stops being manageable, and because the Go service handles the version switching, the users and the editing while the static site generator keeps doing what it is good at.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 127 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What it is, and the warning at the top
Kalmia is a Go tool with a web interface for managing RsPress documentation, and the project description calls it a documentation CMS. The readme opens with a warning that is worth taking at face value: this project is still in development, so expect breaking changes.
That warning is the frame for everything else here, because a documentation CMS that breaks between releases is a documentation CMS that has to be upgraded carefully, and the readme's own pointer to the upgrade path for existing installations reflects that.
The feature set is three things. Multiple versions, so a documentation set can carry, for example, a 1.x and a 2.x of the same product. Multiple users, so more than one person can edit. And a markdown editor for easy edits, so the change to a page does not require a pull request and a git workflow.
That combination is the interesting part. RsPress is a static site generator, and a static site generator is excellent at producing a fast site and poor at letting a non-technical person fix a typo. Kalmia puts a service in front of the markdown files, and the service is Go, which is why the repository is mostly Go with a separate web/ directory for the front end.
The primary language in the repository metadata is TypeScript, which is the front end, and the Go side is the service. The module path in the go.mod file is git.difuse.io/Difuse/kalmia, and the code is on a self-hosted forge as well as on GitHub.
The licence is AGPL-3.0, the default branch is master, and the recent releases are 0.2.0 from 2025-08-05, 0.1.0 from 2025-02-16 and 0.0.2 from 2024-09-10, with the last push to the repository on 2026-06-17. The website is at kalmia.difuse.io.
The build, and a documentation mismatch worth knowing about
The build is driven by a Makefile, and the top-level targets are the ones you will use.
make all
make deps
make test
make build
make cleanThe all target is deps followed by build, the deps target downloads the Go modules, the test target runs go test across the tree, and the build target produces the binaries for every supported platform. There are also per-platform targets, and the pattern is a set of cross-compilation rules that set GOOS and GOARCH and write the result into dist/ with the application name, the version and the platform in the file name.
The full matrix in the Makefile is wider than most people expect: Linux on amd64, 386, arm, arm64 and riscv64, Windows on amd64, 386 and arm64, FreeBSD on amd64 and arm64, and macOS on amd64 and arm64. Every one of them is the same two-line rule with a different pair of environment variables and a different output name, which is the cheapest possible way to support a wide platform range in Go.
There is a documentation mismatch here that will waste somebody's time. The readme lists the per-platform targets as make build-amd64-linux, make build-arm64-linux, make build-win64, make build-freebsd64, make build-macos-arm64 and make build-macos-amd64, while the Makefile itself defines build-linux-amd64, build-linux-arm64, build-windows-amd64, build-freebsd-amd64 and build-macos-amd64, with the operating system before the architecture. Most of the readme's names match no Makefile target, so a contributor who follows the readme literally gets a No rule to make target error for most of them.
It is a small thing, and it is exactly the kind of small thing that a documentation CMS project has no excuse for, since the documentation is the product. But it is also a fair illustration of the state of the project.
The web front end has its own target, build-web, which installs the pnpm dependencies and runs the build with rsbuild, so the two halves of the application are built by separate toolchains and the Go binary is expected to find the built assets.
The Go dependencies, and what they say the tool does
The dependency list in the go.mod file is a good description of the feature set without any prose.
The documentation rendering is goldmark, the CommonMark parser, with goldmark-figure for figure extensions, and goquery for querying HTML, which is what a markdown editor and a content preview need.
The editing and version side is go-git, the pure Go implementation of Git, which is how a service that lets people edit markdown files on a server keeps the files in version control. That is a deliberate choice, because the alternative is a database of content that the static site generator then has to be told to read.
The web layer is gorilla/mux for routing, go-playground/validator for request validation, golang-jwt for tokens, and the AWS SDK and MinIO client libraries, which is how the object storage for uploads is reached. The clarketm/json library suggests a JSON abstraction layer over the standard encoder, and mimetype detects content types from the bytes rather than from the extension.
Storage is GORM, with the Postgres driver and a pure Go SQLite driver, glebarez/sqlite, which is what lets the same binary run against a file for development and against PostgreSQL in production without cgo. Logging is zap. And there is an oauth2 dependency plus golang.org/x/crypto, which is consistent with the service talking to a Git host, and the presence of google/go-github in the dependency list points at GitHub specifically as one provider.
So the picture is a service that edits markdown in a Git repository, renders a preview of it, stores uploaded assets in object storage, and serves a small multi-user web application for doing all of that. That is a coherent and fairly small design, and the dependencies are all mainstream Go libraries with no exotic choices, which matters for a project that is still moving.
The module requires Go 1.25.0, while the readme's requirements section says Go 1.22 or later, and the container build uses golang:1.25-bookworm.
The container image, and the object storage decision
The Dockerfile is a three-stage build and it is worth reading because it explains how the Go binary and the web assets end up in one artefact.
The first stage is the web builder on node:24, with corepack enabling pnpm at version 10 and a pnpm install using the frozen lockfile with a cache mount on the pnpm store, followed by the pnpm run build. The second stage is the Go builder on golang:1.25-bookworm, which downloads the modules, copies the built web assets in, and compiles with CGO_ENABLED=0 go build and the -s -w flags, which strips the symbol table and the DWARF data. The final stage is node:24-slim, which contains only the compiled binary and the built web assets, and it runs as the node user rather than as root.
So the runtime image is not a Go image at all, it is a Node image with a static Go binary in it, and the Go side needs no C library at runtime, which is why CGO is disabled in the build. That is the standard way to ship a Go service with a JavaScript front end, and it explains the node:24-slim base and the 2727 port.
The compose file is where the operational decisions show. There is an app service built from the Dockerfile with the port published as 2727 by default and a config.json mounted into the container, and it waits for PostgreSQL to be healthy. The postgres service is PostgreSQL 15 with a pg_isready health check, and the credentials are required variables rather than defaults, which is the right choice. The MinIO service provides object storage on the S3 API port and the console port, with the uploads bucket created by a separate one-shot job that sleeps, sets the alias, makes the bucket and then sets anonymous download on it.
That last line is the one to look at twice. Anonymous download on the uploads bucket means anything uploaded is publicly readable by URL, which is the right default for documentation images and the wrong default for anything else. Since the compose file is the reference deployment, a team that copies it for a different purpose inherits that behaviour, and it is worth noticing before the first upload rather than after.
Running it, and the local development loop
Running a built binary is straightforward. The executable ends up in the dist directory, and you run it with:
cd dist && ./kalmia_<version>_<platform>and then visit the admin interface at http://localhost:2727/admin to start using it.
One configuration detail is stated with the emphasis it deserves: there should be a config.json file in the same directory as the executable, or you can specify the same with the -config flag. A binary that looks for its configuration relative to its own location is convenient for the release layout, where the executable, the web assets and the configuration travel together, and it is a trap for a user who moves the binary somewhere else and wonders why it will not start. The -config flag is the way out.
The requirements are Go 1.22 or later in the readme, Node.js v20.15.0 or later, npm, pnpm version 10 or later, and PostgreSQL 15 or later if you are not using SQLite. The pnpm install command is given as npm install -g pnpm@latest-10.
The development loop is where the project's two halves separate, and the readme is clear about it. The team primarily uses SQLite while developing, so there is no database to set up. In the web/ directory you run npm install and then npm run start, and in the root directory in a separate terminal you run air ., which starts the Go server with hot reload, and you need Air installed. The presence of a .air.toml file in the repository is the configuration for that reload loop, and a devcontainer and a docker-compose-dev.yml are also in the tree.
The contributor guidance is one sentence, submit a pull request, and the combination of a working dev loop, a Dockerfile and a compose file means the project is set up for outside contributors even though the codebase itself is small.
Editorial conclusion
Kalmia is worth evaluating if you publish versioned documentation on RsPress, because a documentation set with several versions and several editors is exactly the case where a folder of markdown stops being manageable, and because the Go service handles the version switching, the users and the editing while the static site generator keeps doing what it is good at. It is the wrong tool if you have one version of one small site, and it is the wrong tool if you need a content platform rather than a documentation system. Before you build on it, check three things: that you are ready for a moving target, because the project states it is still in development and expects breaking changes, and the release history is 0.0.2, 0.1.0 and 0.2.0 across 2024 and 2025; that you read the AGPL-3.0 licence, which for a web application that serves an admin interface over HTTP is a stronger obligation than the permissive licences a documentation tool would usually carry; and that you check the deployment assumptions, because the compose file runs PostgreSQL, MinIO and a bucket creation job with an anonymous download policy on the uploads bucket, which is a sensible default for a documentation system serving media and a poor default for anything else.
Frequently asked questions
What is Kalmia and what does it manage?
It is a Go tool with a web interface for managing RsPress documentation, supporting multiple documentation versions, multiple users and a markdown editor for easy edits. The Go service edits the markdown in a Git repository, renders a preview, stores uploaded assets in object storage and serves the admin interface on port 2727.
How do I build and run Kalmia?
Clone the repository, install the requirements of Go, Node.js 20.15 or later, pnpm 10 and optionally PostgreSQL 15, then use the Makefile: make all builds everything, make deps downloads the Go modules, make test runs the tests and make build produces the binaries for all supported platforms. Run the executable from the dist directory, with a config.json beside it or the -config flag pointing at one, and open http://localhost:2727/admin.
What database and storage does the Kalmia Docker deployment use?
The compose file runs the app against PostgreSQL 15, with a required user and password and a pg_isready health check, and it runs MinIO for object storage with an uploads bucket. A separate one-shot job creates that bucket and sets anonymous download on it, which is a sensible default for documentation images and worth changing if the uploads are anything else.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/difusehq-kalmia)Was this useful?