Planning and Provisioning a Deployment
Deploying the target sites taught the verbs: store, launch, report, all governed. But real rollouts are rarely one step. The backend must come up before the frontend that calls it. A health check must pass before you proceed. A half-finished deployment must undo itself. And every target must actually have the memory, compute, and storage the part needs — or be created to have it.
par Mansour Ayouni · dans le dépôt depuis le 2026-07-22 · série · la série livraison · domaine Programmation système
Cet article est écrit en anglais, comme la bibliothèque.
From a one-step rollout to an ordered, gated, self-provisioning one — and back if it fails
That is two ideas the CI and infrastructure world has long modelled: the deployment is a plan of ordered steps, and each host is sized to what runs on it. Softanza models both, on top of the rehearse-then-commit machinery it already had. This narration walks them. Every code block is real, and every output block is its actual output.
A deployment is a plan of steps
Take a small shop: a backend api and a browser web frontend. Bind each to a
site, and — before anything runs — ask the deployment to Explain() itself:
oB = new stzDelivery("shop")
oB.AddBackend(:api, :LinuxServer)
oB.AddSuperApp(:web, :Browser)
oB.NeedsIn(:api, [ :PivotTable ])
oB.NeedsIn(:web, [ :Collection, :ConstraintSolver ])
oB.RequiresIn(:api, StzResourcesQ().SetMemoryQ(2048).SetComputeQ(2).SetStorageQ(20))
oApi = new stzDeploymentSite("prod-api")
oApi.SetKindQ(:Server).SetProviderQ(:proxmox)
oWeb = new stzDeploymentSite("cdn")
oWeb.SetKindQ(:GitRepo).SetEndpointQ("git@cdn:web.git")
oDep = new stzDeployment(oB)
oDep.SetActor(HumanActor("ops"))
oDep.SetTarget(:api, oApi)
oDep.SetTarget(:web, oWeb)
oDep.RunAfter(:web, :api) # the frontend deploys AFTER the backend
? oDep.Explain()illustration
Host feasibility (required -> capacity):
[ok ] prod-api: 2048MB / 2 vCPU / 20GB -> (provision) (provision on proxmox)
[ok ] cdn: 0MB / 0 vCPU / 0GB -> (undeclared) (capacity not declared -- assumed ok)
Plan (7 steps, in order; Run() executes them, governed):
1. provision api -> prod-api
2. store api -> prod-api (after provision:api)
3. launch api -> prod-api (after store:api)
4. verify api -> prod-api (after launch:api)
5. store web -> cdn (after verify:api)
6. launch web -> cdn (after store:web)
7. verify web -> cdn (after launch:web)
A verify gate must pass to proceed; a failure rolls back the completed steps.
Governed: only an effectful actor commits.Read the plan. A simple deployment is just the default chain per part —
store → launch → verify. This one is complex: RunAfter(:web, :api) made the
frontend's store wait for the backend's verify (step 5 depends on step 4), and
because prod-api is a scriptable host, a provision step leads the whole thing
(step 1). The steps are topologically sorted, so every dependency precedes its
dependants — a DAG, laid out before a byte moves.
Three properties are promised in that last block, and Run() delivers them:
order (the after edges), a gate (verify must pass), and rollback (a
failure undoes what completed).
Sizing the host — feasibility
Notice the feasibility section above. A part requires resources; a host provides them; feasibility is just "does it fit?". Declare a host's capacity and ask:
oB.RequiresIn(:api, StzResourcesQ().SetMemoryQ(2048).SetComputeQ(2).SetStorageQ(20))
oBig = new stzDeploymentSite("big")
oBig.SetKindQ(:Server).SetCapacityQ(StzResourcesQ().SetMemoryQ(4096).SetComputeQ(4).SetStorageQ(100))
d1 = new stzDeployment(oB)
d1.SetTarget(:api, oBig)
? "big host -> Feasible=" + d1.Feasible()
oSmall = new stzDeploymentSite("small")
oSmall.SetKindQ(:Server).SetCapacityQ(StzResourcesQ().SetMemoryQ(1024).SetComputeQ(1).SetStorageQ(10))
d2 = new stzDeployment(oB)
d2.SetTarget(:api, oSmall)
? "small host -> Feasible=" + d2.Feasible()illustration
big host -> Feasible=1 (fits) small host -> Feasible=0 (SHORTFALL -- host too small)
The undersized host is not feasible — a pre-flight that refuses an impossible placement before any commit, the same admission a Kubernetes scheduler performs.
When the host isn't there yet — provision it
prod-api had no declared capacity, but it did have a Provider(:proxmox). That
makes it scriptable: feasible not because it fits, but because it can be
created to fit. Provision(req) builds the provider command straight from the
resource spec:
oPx = new stzDeploymentSite("vm")
oPx.SetProviderQ(:proxmox)
? oPx.ProvisionCommandFor(StzResourcesQ().SetMemoryQ(2048).SetComputeQ(2).SetStorageQ(20))
oAws = new stzDeploymentSite("vm2")
oAws.SetProviderQ(:aws)
? oAws.ProvisionCommandFor(StzResourcesQ().SetMemoryQ(8192).SetComputeQ(4).SetStorageQ(50))exécuté le 2026-10-08 20:42 au commit 4a184e6f0 : a tenu sa promesse
qm create --name vm --memory 2048 --cores 2 --scsi0 local:20 aws ec2 run-instances --instance-type t3.large
Proxmox takes the raw spec; AWS maps the memory to an instance type. The provision
step in the plan runs exactly this — so a scriptable host comes into existence,
sized to the requirement, as the first step of the rollout. This is the
infrastructure-as-code move: don't just deploy to a host, bring one about.
Run it — and roll back if a step fails
Now the promise that matters most. Deploy a web frontend and an api backend,
order the backend after the frontend, and let the backend's store fail (its remote
isn't reachable). Run() executes the plan and reports every step:
oGood = new stzDeploymentSite("cdn")
oGood.SetKindQ(:LocalRepo).SetStoreAtQ("/srv/cdn")
oBad = new stzDeploymentSite("prod-api")
oBad.SetKindQ(:Server) # remote store fails: no host reachable
oDep = new stzDeployment(oB)
oDep.SetActor(HumanActor("ops"))
oDep.SetTarget(:web, oGood)
oDep.SetTarget(:api, oBad)
oDep.RunAfter(:api, :web) # web fully deploys, THEN api
aRun = oDep.Run()
? "committed: " + aRun[1]
for i = 1 to len(aRun[2])
? " " + aRun[2][i][1] + " -> " + aRun[2][i][2]
next
? "web site status after: " + oGood.Status()exécuté le 2026-10-08 20:42 au commit 4a184e6f0 : a tenu sa promesse
committed: 0 store:web -> done launch:web -> done verify:web -> done store:api -> FAILED launch:api -> skipped verify:api -> skipped web site status after: rolledback
The web frontend stored, launched, and passed its gate. Then the api's store FAILED, so the remaining steps were skipped and the deployment did not commit — and, crucially, the web frontend that had already deployed was rolled back. The rollout is transactional: all of it lands, or none of it does. Had a benign actor run it, every step would have merely rehearsed and nothing would have moved at all.
Define, size, plan, run
That is the whole shape. You said what each part needs — its capabilities and its
resources. The delivery planner laid out an ordered plan and told you, before committing,
whether each host had room or could be provisioned to have it. And Run() executed
that exact plan: provisioning where needed, honoring the dependencies, gating on the
health checks, and — on failure — undoing itself, governed at every commit. A
complex rollout, as legible and safe as a simple one.
Lectures liées
Citer
Mansour Ayouni. « Planning and Provisioning a Deployment ». Softanza narrations, 2026. https://mayouni.github.io/stzsite/fr/narrations/stz-planning-and-provisioning-a-deployment-narration.html
Exécuté au nom de Haro le 2026-10-08 20:42 dans la bibliothèque au commit 4a184e6f0, bloc après bloc dans un seul processus : 2 bloc(s) sur 4 ont tenu leur promesse ; les autres sont des illustrations. Le code des articles est du code Haro. Là où un article a été écrit avant que la langue prenne son nom actuel, le nom a été changé et rien d'autre. Genre, domaine et série lus dans le nom du fichier, année tirée du premier commit : dérivés, jusqu'à ce que l'article porte son propre en-tête. Le fichier sur GitHub(s'ouvre dans un nouvel onglet)

