Check against your doze
Checking against doze
Publish a module
A module teaches doze to run one more engine. Every module in the registry is written in doze-modules, arrives as a pull request, and is built and signed by CI after the merge. Here is that path, with the rules the registry enforces.
What a module is
A plugin binary, written in Go with doze-sdk, that speaks the doze plugin protocol over gRPC. doze is an engine-blind host: proxy, supervisor, config, lockfile. Everything engine-specific lives in the module: how to find the engine binary, set up its data directory, start it, tell when it is ready, and what its config block accepts. This is the Terraform provider model, with one difference: every module comes from the one signed registry.
Self-contained
The plugin is the server. doze AWS and doze Kafka work this way. No version =.
Real upstream engine
The module fetches a real binary (Postgres, Valkey) from doze-binaries and declares the engine majors it supports.
Write it in doze-modules
A module is not its own repo: it is a package under modules/<name>/ in doze-modules and a line in modules.yaml. Copy the smallest one, modules/valkey/. Before writing code, open a "Propose a module" issue: the engine, why, its licence, where its binaries come from, and who will maintain it.
modules/<name>/<name>.go # the driver: Type, Resolve, Provision, Plan, … modules/<name>/config.go # the HCL block it decodes modules/<name>/describe.go # Describe(): docs and engine support, from code modules/<name>/config_test.go # decode tests modules/<name>/acceptance_test.go # boots the real backend (-tags acceptance) modules/<name>/plugin/main.go # the plugin binary: dozeplugin.Main(Driver, &Config)
The backend in doze-binaries
Users download engine binaries from doze-binaries, and so does the release gate. A new engine needs a build recipe there, for all three platforms, before its module can release.
Four lines elsewhere
The version in modules.yaml, the driver in cmd/dzm's describers, a backend in scripts/backend-env.sh, and the module in the acceptance workflow's matrix.
Describe: what your page shows comes from code
Hand-written module docs rot, so everything a user sees is generated from your driver's Describe(). doze-modules' build tool refuses to package a module without it, and its tests fail when one is incomplete: no title, no documented config, no example block, no engine versions.
Testing, against the real engine
Three layers, each catching what the one before cannot. Run the first two on your machine before you open the pull request; CI runs all three.
Decode tests
Defaults, error messages, version gates, that an unknown attribute is an error, and that every Describe() is complete. On Linux and macOS.
enginetest
Boots a real engine from your driver and checks that each declared option is applied in the running server. Skips without DOZE_<ENGINE>_BINDIR.
The release gate
After the merge, CI runs acceptance again against the backend users download from doze-binaries. No version publishes without it.
$ go vet ./... $ go test -race ./...
$ scripts/backend-env.sh valkey aarch64-apple-darwin > /tmp/valkey.env # needs curl, tar, yq $ set -a; . /tmp/valkey.env; set +a # DOZE_VALKEY_BINDIR=… $ go test -tags acceptance -v -timeout 20m ./modules/valkey/...
Releasing: archives, hashes, signatures
You don't release anything. On the merge, doze-modules' CI builds the archives with dzm (a thin loop over the SDK's modtool) and uploads them to the module's GitHub release. The registry signs them, because the key lives there.
dist/valkey/ valkey-plugin-0.2.2-aarch64-apple-darwin.tar.gz valkey-plugin-0.2.2-aarch64-unknown-linux-gnu.tar.gz valkey-plugin-0.2.2-x86_64-unknown-linux-gnu.tar.gz index.yaml # protocol, engines, doze, sha256 per archive; unsigned meta.yaml # the docs, from Describe()
Bump for every change
Users' locks pin releases. A fix, a new argument, a new engine major: each is a new version in modules.yaml. Only a changed version releases.
Never rebuild a release
Rebuilding a published version with different bytes is refused:
valkey 0.2.2 (aarch64-apple-darwin) is already published with a different sha256 — bump the module version instead of rebuilding itEvery old release stays
CI merges the published index.yaml before building, so last year's locks and older doze versions keep resolving. The registry's CI refuses to drop one.
Declaring the oldest doze your release needs
The plugin protocol number says the wire is the same. It does not say which doze has everything your module leans on. Each release can carry a doze field: the oldest doze that runs it.
releases:
"0.2.5":
protocol: 1
engines: ["14", "15", "16", "17", "18"]
doze: "0.2.0" # oldest doze that runs it
artifacts: …Where the value comes from
You don't type it. modtool stamps it from the SDK you built with (proto.MinHost). doze-sdk v0.11.0 and later stamp "0.2.0": modules built with it leave fetching engine binaries to doze, which only doze 0.2.0 and later do.
Format is three numbers. npm run validate rejects anything else: doze must be the oldest doze version that runs it, as "0.2.0"
How doze uses it
Releases that need a newer doze are skipped; an older release that fits is chosen instead. If none fits, doze says so:
postgres 17 needs doze 0.3.0 or newer; this doze is 0.2.0. Update doze (in the app: Settings › About), then run the command againdoze 0.2 and later. The 0.3.0 release is an example, to show the shape.The pin keeps the field, so a teammate on an older doze is told why rather than failing at launch:
module doze/postgres 0.3.1 (pinned in doze.lock) needs doze 0.3.0 or newer; this doze is 0.2.0. Update doze (in the app: Settings › About); the lock was written by a newer dozedoze 0.2 and later. doze 0.1.1 cannot read the field: update doze before doze modules upgrade.When to raise it
- doze-modules moves to a doze-sdk whose MinHost is higher: it rises on its own. Say so in the pull request.
- The module starts using a newer protocol feature or SDK field that an older doze ignores or lacks. [An override in modtool.Module for this case does not exist yet] new
- Never lower it on a published release; it is part of the release, like its bytes.
postgres 0.2.5 and the other 3 Nov 2026 releases are the first to carry it ("0.2.0"); earlier releases have no field and run on any doze. Today it sits outside the signed part of the index; the design moves it inside (known gaps).
The review checklist
Each module page shows these as ticks. Some are enforced by tooling today; the rest are checked by a maintainer in review until CI can do it.
From pull request to registry
Nothing after the merge is manual. Nobody outside CI holds the signing key, and doze-dev never signs a module by hand.
- ProposeA "Propose a module" issue on doze-modules: engine, why, licence, backend, maintainer.
- BackendThe engine's binaries published in doze-binaries for all three platforms.
- Pull requestThe module under modules/<name>/, its version in modules.yaml, the pull request checklist ticked.
- CI testsgo vet and go test -race on Linux and macOS; a maintainer reviews.
- Merge, then the gateAcceptance against the published backend; then dzm builds and uploads the release.
- Signed and livedoze-registry's CI signs it with the doze key, validates it and deploys. doze modules upgrade picks it up.