Deploying config
Build CONFIG_DIR once from a tag of your config repository, deploy the same artifact everywhere, and let backd refuse instances that run anything else.
Everything backd serves comes from CONFIG_DIR: realms, schemas, indexes, access rules and function code. Every instance of a deployment must run exactly the same config. If two instances ran different rules or different function code, the same request could be allowed on one and refused on the other.
So the config is treated like code:
- it lives in one config repository per deployment;
- CI builds it at one pinned tag into an immutable artifact: an image with
backdand the config inside; - staging and production run that same artifact;
backdchecks it (see what backd enforces).
The config repository
backd template project --dir <directory> --realm <realm> scaffolds the repo in one step — the sample database and function, a compose.yaml development stack on the released images and a check-only backd.yml workflow — so you don’t have to assemble it by hand; see Starting from a template. Deploying adds two files of your own: the Dockerfile and a packaging workflow, like the ones below.
my-backd-config/
config/ CONFIG_DIR
shop/
realm.yaml
orders/
items/schema.json …
_functions/ functions, with deno.json, deno.lock, lib/
compose.yaml a local stack to develop against (from the template)
.github/workflows/backd.yml checks the config on every push (from the template)
Dockerfile the artifact: backd + config (yours to add)
.github/workflows/ci.yml build, check, package on tag (yours to add)
The Dockerfile starts from a released backd image and adds the config. backd config check runs during the image build, so an invalid config never becomes an image:
FROM ghcr.io/fernandezvara/backd:v0.9.0
COPY config /config
ENV CONFIG_DIR=/config
RUN ["/backd", "config", "check"]
Verifying a release
Pinning a version isn’t trusting one. Each tagged release is published automatically, checked and verifiable:
- the binaries (Linux, macOS, Windows; amd64 and arm64) come with
checksums.txtand an SPDX SBOM each (*.sbom.json) on the GitHub Releases page; - the image is scanned for known vulnerabilities before it’s published, and carries SBOM and provenance attestations;
- the image is signed with cosign using GitHub’s identity — no keys to manage:
cosign verify ghcr.io/fernandezvara/backd:v0.9.0 \
--certificate-identity-regexp '^https://github.com/fernandezvara/backd/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
backd version prints the version of a binary or image.
Developing locally
The template’s compose.yaml already gives you this out of the box — released images and dev mode, no builds. The equivalent minimal stack, if you write your own, is MongoDB (a single-node replica set) and backd on your config, with PROVISION_MODE=apply so every restart applies your changes:
# compose.yaml
services:
mongo:
image: docker.io/library/mongo:8.2
command: ["--replSet", "rs0", "--bind_ip_all"]
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "try { rs.status() } catch (e) { rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'mongo:27017'}]}) } if (!db.hello().isWritablePrimary) quit(1)"]
interval: 2s
retries: 30
backd:
image: ghcr.io/fernandezvara/backd:latest
command: ["serve"]
volumes: ["./config:/config:ro"]
ports: ["127.0.0.1:8080:8080"]
environment:
MONGO_URI: mongodb://mongo:27017/?replicaSet=rs0
depends_on:
mongo: { condition: service_healthy }
After changing schemas, rules or realm.yaml, restart backd (docker compose up -d --build backd). After changing a function, run backd functions build first: backd refuses stale bundles — or run backd locally (not in the container) with BACKD_DEV=true to reload the config and rebuild functions on save, and backd functions invoke to call one directly; see Dev mode.
Before pushing, run the same checks CI will run:
CONFIG_DIR=./config backd functions build # if you have functions
CONFIG_DIR=./config backd config check
CI: build, check, package
On every push, CI builds the function bundles, checks the config and publishes an image named after the commit. A GitHub Actions example:
name: config
on:
push:
branches: [main]
tags: ["v*"]
env:
BACKD_VERSION: 0.9.0
IMAGE: ghcr.io/my-org/backd-config
jobs:
build:
runs-on: ubuntu-latest
permissions: { contents: read, packages: write }
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v2
with: { deno-version: 2.9.7 } # the Deno this backd version bundles with
- name: Install backd
run: |
curl -fsSL "https://github.com/fernandezvara/backd/releases/download/v${BACKD_VERSION}/backd_${BACKD_VERSION}_linux_amd64.tar.gz" | tar -xz backd
sudo mv backd /usr/local/bin/
- run: backd functions build
env: { CONFIG_DIR: ./config }
- run: backd config check
env: { CONFIG_DIR: ./config }
- uses: docker/login-action@v3
with: { registry: ghcr.io, username: "${{ github.actor }}", password: "${{ secrets.GITHUB_TOKEN }}" }
- name: Image for this commit
run: |
docker build -t "$IMAGE:${GITHUB_SHA::12}" .
docker push "$IMAGE:${GITHUB_SHA::12}"
- name: On a tag, the same image under the tag (no rebuild)
if: startsWith(github.ref, 'refs/tags/')
run: docker buildx imagetools create -t "$IMAGE:${GITHUB_REF_NAME}" "$IMAGE:${GITHUB_SHA::12}"
backd config fingerprint prints the config’s fingerprint; CI can print it next to the image name, so you can match a running instance to its build.
Promoting and rolling back
- Tag a commit (
v12). CI publishes the image of that commit under the tag. Nothing is rebuilt. - Staging: run the provision step with the tagged image and an admin
MONGO_URI(backd provision), then roll out the instances withPROVISION_MODE=verify. - Production: the same two steps with the same image.
- Rollback: deploy the previous tag the same way: provision, then roll out.
Provision before rolling out, every time. The production reference does it with its provision job.
What backd enforces
backd provision (and backd serve in apply mode) records the config’s fingerprint in MongoDB, for each realm it serves: in the backd___deployment database, one record per realm, so separate deployments sharing a MongoDB cluster keep their own.
Instances in PROVISION_MODE=verify compare their config’s fingerprint with the one recorded for each of their realms, and refuse to start when it differs or none was recorded:
backd serve: the config differs from the one last provisioned: this instance's config is 7c1e…, but
realm shop: provisioned 4a90… (at 2026-09-28T10:00:00Z by backd v0.9.0)
provision this config (`backd provision`) or deploy the config that was provisioned
Instances that are already running aren’t stopped: during a rollout, old instances keep serving until they’re replaced.
- The fingerprint is a hash of every file
backdreads fromCONFIG_DIR:realm.yaml, each collection’sschema.json,indexes.jsonandcollection.yaml, and each functions project with its bundles. Filesbackdignores (.git, aREADME.md) don’t change it, so the same tag gives the same fingerprint on any machine. - Every instance logs its fingerprint at startup (
"msg":"config loaded") and reports it in/readyz:{"status":"ready","config":"<fingerprint>"}. backd config fingerprintprints it without starting anything.- The database user of instances in
verifymode needs to readbackd___deployment.realms;backd databases --collectionslists it with the others for your grants. Only the provision step writes it.