Contributing to these docs
Contributing to these docs
The site is a docmd project in apps/docs. Pages are Markdown files. You do not need to know docmd to write one.
Two sections, two rules
Docs is for people who will use Mesh. Architecture is for contributors: what cannot be understood by looking at a single code file.
Architecture documents what exists. If a milestone adds a contract, a pipeline stage or a cross-package rule, its Architecture page is updated in the same pull request.
Docs is a live spec: pages under Docs are written before the implementation, and sometimes before the architecture is settled, to model how using Mesh should feel. This replaces the earlier practice, “the pages under Docs describe only what exists today”.
The reason is that writing the page is a test of the design. A page that has to say “the architecture does not say what this command is called” has found a gap; a page that has to contradict two architecture pages has found a contradiction. Neither shows up by reading the architecture.
Two things follow from that rule:
- Every page under Docs opens with the same warning callout, saying it is a live spec of how things will be and that Mesh is not released. Copy it verbatim from another Docs page.
- Where a decision is Proposed or open, the page takes the option the decision record recommends and adds a short
::: callout info "Not decided yet"linking to the record. Never pick silently. - Where nothing is decided at all, design the simplest thing a TypeScript developer would expect and record it in the task report as an invention, with the alternative you rejected. Those are the most valuable output of the work.
The ruling is in the rulings of 2026-10-04, row “User docs as live spec”.
Build and preview locally
Use Bun, never npm. The repository is a Bun workspace, so install once at the repository root. The docs site’s scripts run from apps/docs:
bun install # at the repository root
cd apps/docs
bun run dev # live preview, default port 3000
bun run build # static site into apps/docs/site/
bun run validate # check internal links
A docmd plugin that docmd.config.json enables must also be declared in apps/docs/package.json (for example @docmd/plugin-search), or docmd shells out to npm at build time because it does not recognise Bun’s text bun.lock.
Run bun run build and bun run validate before you open a pull request. From the repository root, bun run verify runs them together with the tests and the type check. Stop the dev server when you are done.
Layout and file names
Pages live under apps/docs/docs/.
docs/
index.md site landing page
docs/ Docs section (for users)
architecture/ Architecture section (for contributors)
roadmap/ decisions/ overview/ in-depth/ research/
- File names are lowercase, hyphenated, and end in
.md:durable-engines.md. - Every folder has an
index.mdthat says what belongs in it. - Decision records are numbered:
0001-use-bun-only.md. The number never changes and is never reused. - Research documents are named for the topic, not the date:
durable-engines.md. Put the date in the page.
Frontmatter
Every page starts with:
---
title: "Page title"
description: "One sentence."
---
Add a page
- Create the
.mdfile in the right folder. - Add it to
navigationinapps/docs/docmd.config.json. A page that is not listed is built but not shown in the sidebar. Each area is a group with an “About …” first entry; add your page as another object in that group’schildrenarray:{ "title": "Durable engines", "path": "/architecture/research/durable-engines/" }. - Link to it from the folder’s
index.md. - Run
bun run buildandbun run validate.
Add a decision record
- Copy the template to
decisions/NNNN-short-title.md, with the next free number. - Replace the template’s frontmatter
titleanddescription, and delete itsnoindex: trueandllms: falselines. Fill in every heading. Write “none” rather than deleting one. - Set the status to
Proposed. Change it toAccepted,RejectedorSuperseded by NNNNwhen it is settled; never delete a record. - Add it to
navigationunder Decisions and to the list indecisions/index.md.
Add a research document
- Create
research/topic.md. - State the question, the date, and what was checked. Link every claim to its source. Check each claim against the source before you write it down.
- Say what the research changed, and link the decision record if there is one.
- Add it to
navigationunder Research and toresearch/index.md.
Linking
Link to other pages with a relative path to the .md file, for example ./roadmap/index.md from architecture/index.md. bun run validate checks these, including links inside code spans, so use real paths in examples. Do not use absolute URLs for pages in this site.
Deployment
The site is deployed at https://mesh.saulo.tech by a Coolify instance running on the operator’s server. In Coolify it is the application mesh-docs in the project mesh, built from https://github.com/svallory/mesh, branch main, base directory apps/docs, build pack dockerfile. HTTPS is forced and the certificate is issued by Let’s Encrypt through Coolify’s Traefik proxy.
The build is defined in the repository, not in the Coolify UI: apps/docs/Dockerfile (an oven/bun:1.3.14 stage that installs the docs app and runs bun run build, then an unprivileged nginxinc/nginx-unprivileged stage that serves site/ on port 8080) plus apps/docs/nginx.conf. Both base images are pinned by version and sha256 digest; bump the tag and the digest together. The Dockerfile installs the docs app on its own, not through the root workspace, because the root bun.lock references the local link: MX packages of packages/compiler, which a build container does not have. The image therefore has its own lockfile: apps/docs/docker/package.json and apps/docs/docker/bun.lock, installed with bun install --frozen-lockfile, so every dependency version is pinned. bun run validate fails when the docmd versions in that lockfile differ from apps/docs/package.json; after changing a docmd version, update docker/package.json to match and regenerate docker/bun.lock with bun install in a copy of docker/ outside the workspace. The image build never sees the workspace root, so root-level overrides, patchedDependencies and trustedDependencies do not apply to it.
The application depends on these Coolify settings: build pack dockerfile, base directory /apps/docs, branch main, exposed port 8080, health check path / on port 8080, and watch paths apps/docs/**. Coolify stores the proxy labels when the application is created and does not regenerate them when the exposed port changes. After a port change, reset or edit the stored labels so they point at the new port; otherwise the proxy returns 502 even though the container is healthy.
A deploy is triggered automatically by a push to main that touches apps/docs/**: a GitHub webhook on the repository (push events) calls the Coolify webhook endpoint, and Coolify rebuilds and redeploys the application. The application’s watch paths are set to apps/docs/**, so pushes that touch only other packages do not rebuild the site. A manual redeploy needs operator access: press Redeploy on the mesh-docs application in the operator’s Coolify dashboard, or run coolify deploy uuid <application-uuid> with the coolify CLI and a Coolify API token.
Changes to apps/docs/Dockerfile, apps/docs/nginx.conf, apps/docs/.dockerignore or apps/docs/docker/ take effect on the next deploy; test them first with docker build apps/docs from the repository root.