Deploying it
Deployment has three declarations, and they answer different questions.
An environment says what exists: which components are present and which variant each resolves to. It names no machine.
A target says where one kind of component can land — a cluster, a release
channel, a CDN bucket. Targets live in targets.toml at the project root, so
several systems can land in the same place without describing it twice.
A deploy config joins them: it names one environment and binds a target for each component kind in it. Two configs may name the same environment and land it in different places, which is how production and a preview are the same system.
environments = ["local", "cluster", "gcp"]
[deploy-configs.local]
environment = "local"
dev-loop = true
online-service = "local"
[deploy-configs.gcp]
environment = "gcp"
deploy-name = "todo"
online-service = "cluster-gcp"
web-assets = "assets"
[deploy-configs.preview]
environment = "gcp"
online-service = "cluster-gcp"
web-assets = "assets"
The development loop
b deploy local
dev-loop = true is the only difference between this config and a released one.
It changes how the components you are editing run, and nothing else:
- The page runs behind its toolchain's own dev server, with hot reload.
- The api runs as a process on your machine, with the cluster's traffic for it routed to that process and the pod's configuration handed to it.
- Everything else — Couchbase, Curity, Postgres, the gateway — stands in a cluster on your machine exactly as it stands in production.
So the thing you are editing is a local process you can attach a debugger to, while what surrounds it is the real arrangement rather than a stub. That is the one rule about how development differs here: the loop replaces what you are editing and leaves the rest alone.
Production, and a preview beside it
The last two configs name the same environment and land it twice.
gcp carries deploy-name = "todo", which fixes the segment every host is
composed from, so its addresses survive re-converges. That matters because an
identity server's registered redirect URIs point at them.
preview claims no name, so the deploy mints a token and composes every name
from it: its own namespace, its own page origin, its own store bucket, its own
DNS record. Nothing it creates collides with production's, which is what lets it
run beside it rather than over it.
b deploy gcp
b deploy preview
b undeploy preview
What a deploy does
A deploy converges: it builds what needs building, renders the objects, applies them, and prunes what an earlier deploy of the same config left that this one no longer has. It is safe to run repeatedly — a second run against an unchanged system changes nothing.
You do not sequence it. Where a component's state has to exist before its consumers start, that ordering is declared rather than scripted, so Couchbase's bucket is created before the api that reads it comes up.
b undeploy <config> removes what that config put there, reading its own
record of it.
Before the first deploy to a place
b provision <config>
A deploy lands a system in a target; provisioning makes the target exist. They
are separate verbs because a target outlives many deploys, and because creating
one can destroy: b decommission takes a cluster back out and is refused while a
deploy is standing in it.
For a cluster on your machine, provisioning creates it with k3d and needs no cloud credentials. For a cloud target it creates the durable parts too — the reserved address, the DNS record — and those deliberately survive a rebuild.
Before every deploy
b validate
Validation reads the whole file set and cross-references it: a link to an interface nothing serves, a component in an environment whose config binds no target for its kind, an endpoint naming a wire shape nothing declares. It touches no cluster and needs no credentials, so it is the check to run on every edit.
A deploy validates first and refuses if anything fails, so a broken declaration cannot reach a target. It also refuses to release from a dirty git tree, because a published artifact is meant to be a function of a commit.