On this page

Web hosting: the command sequence

The command sequence

For an existing public edge host, use stado web edge declare. The following sequence is the optional Azure provisioning path, not a release prerequisite:

stado fleet key generate wisent-edge
stado config set azure.subscription_id <subscription>
stado config set azure.ssh_public_key "<the public key generate printed>"
stado web edge provision wisent-edge --region <region with the fleet's vnet> \
  --size <VM size> --image <publisher:offer:sku:version matching the size's architecture> \
  --disk-gb <OS disk GiB> --contact <certificate mail address>
stado web edge status
stado web edge hostnames

provision goes through Stado's own Azure provider (crate::providers::azure) and the same subscription, resource group, subnet and SSH key every other Stado-provisioned VM uses, so the edge is cloud capacity the fleet accounts for like any other — not a machine that exists outside the plane. It creates a public address, an edge-only security group opening 80 and 443, a NIC and the VM, in that order, and unwinds them in reverse if any step fails, naming anything Azure would not release.

Region, size, image and OS disk size have no defaults: each is the operator's to name, and a missing one is clap's usage refusal (exit 2) naming the flag. Azure refuses an ARM64 image on an x86-64 size and a size with no capacity in the region; provision passes that refusal through word for word and unwinds what it created. The JSON answer records the region, size, image and disk the edge was created with.

Three things must be true before provision can run, and each refusal names itself. azure.subscription_id and azure.ssh_public_key are configuration bindings, not derived: the edge is rendered with disablePasswordAuthentication, so Azure would refuse a VM with no way in at all. stado fleet key generate <edge target> mints the pair into the selected credential store as stado-ssh-<edge target> and prints the public half, which is the value the second config set takes; the private half never leaves the store, and Stado derives that item id from the target name when it opens the channel. The service principal in the Skarbiec item stado-azure supplies the credential, so nothing is copied by hand but those two identifiers.

provision requires the subscription's RBAC and spending policy to permit the requested resources. Its response names a refused Azure operation and the provider's error. A refusal is not permission to remove a spending policy, raise a limit or purchase capacity.

Selecting an existing edge

stado web edge declare records an existing target, public address and certificate contact. This is independent of how the host was obtained. stado web edge status then reads the declaration, and stado web status checks each product against its selected edge. No command infers a new connection from a running tunnel or silently changes a product's provider.

The edge runs inside com.wisent.stado

One repository runs one service, so the edge is not a unit of its own. The edge host's Stado process runs the reverse proxy when it is started with two options:

stado serve --api --edge-caddy /opt/homebrew/bin/caddy \
  --edge-caddyfile $HOME/.stado/web-edge/Caddyfile

The role starts Caddy with --watch on that file. When the file does not exist yet, it writes one that serves no site, so the process starts before the first stado web route. Every delivery replaces the file and Caddy loads it itself; nothing is restarted. If Caddy exits, com.wisent.stado stops with stado serve component=edge … stopped: the edge proxy <program> serving <path> exited (<status>), and its init system starts it again.

stado web edge hostnames and stado web route first prove that the live com.wisent.stado process on the edge host was started with --edge-caddyfile, by parsing its argument vector the way stado serve does. When it was not, they refuse with the reason and the edge terminates nothing until com.wisent.stado on <host> runs its edge role: declare it with stado serve --edge-caddy --edge-caddyfile $HOME/.stado/web-edge/Caddyfileandstado service ensure stado --host , then reconcile with stado web edge hostnames``. stado web edge status --json reports unit: com.wisent.stado.

stado web edge remove is the one command that undoes it. It deletes the VM, the NIC, the security group and the public address in reverse creation order, waiting for each, because Azure refuses to release an address while an interface still references it and an orphaned address is billed while belonging to nothing. It refuses while any product still names the stado edge — those hostnames' A records point at the address it is about to hand back — and --orphan-hostnames is how an operator overrides that. --keep-resources forgets the declaration and touches nothing on Azure, which is the correct removal for an edge recorded with stado web edge declare rather than created here.

stado web edge remove

Declare a product:

stado web declare preferences-landing \
  --host <target> \
  --port 3210 \
  --hostname preferences.wisent.com \
  --consumer preferences-landing-web

A declaration is one of three kinds: a unit Stado runs (--host, --port and --consumer, all three), a redirect (--redirect-to) or a hostname in front of a directory service (--upstream-service). A unit missing one of its three is refused with a unit declaration needs --host, --port and --consumer; it is never written with an empty host, port or consumer. A --path-prefix mount whose owner cannot be looked up because the declared web products cannot be read is refused naming their problems.

Release it from its checkout, which builds it on the fleet and publishes the tarball into the channel:

stado release submit preferences-landing --channel stable

Install and start the unit, deliver its environment, and verify it:

stado web deploy preferences-landing
stado web status preferences-landing --json

deploy installs the released tarball as a managed unit named for the product's repository, com.wisent.<product> (for example com.wisent.preferences-landing), mints the unit's Skarbiec consumer grant, and for a product that declares a database resolves the credential item for that consumer through stado database resolve and delivers the named field with stado service secret sync. No value is read by the operator and none appears in a command line.

After the restart, deploy asks the unit's own readyz path from the host, one request after another, until the unit answers 200 or stops being the process that was started. It counts no attempts and cuts no request short: after every request it reads the supervisor's record of the unit (launchd's pid, runs and last exit code, systemd's MainPID, NRestarts and ExecMainStatus), and a unit whose process exited, was started again or was unloaded is refused with <host>: com.wisent.<product> never answered 200 on port <port> at <path> — pid <pid> ended without answering 200 (last exit code <code>; runs <before> -> <after>; now <pid or no process>) after <n> attempt(s); the last probe: <nothing answered on the port | answered HTTP <code>>. A unit that is alive and still answering something other than 200 is a unit getting ready, and the command keeps waiting for it.

Web units used to be labelled com.wisent.web.<product>. deploy retires that unit before it starts com.wisent.<product>, because both bind the same port, and prints <host>: <product> retired com.wisent.web.<product>: retired (<detail>). If the old unit cannot be retired, nothing is installed and the command fails with <host>: com.wisent.<product> was not installed, because com.wisent.web.<product> could not be retired and would hold its port: <error>.

Publish the hostname:

stado web route preferences-landing

route reconciles the hostname into the edge, writes the record, and then waits for https://<hostname> to answer over TLS, reporting the elapsed seconds. It does not report success on a 2xx that still carries an x-vercel-id header.

An application with a database and an operator token declares them when it is declared:

stado web declare preferences \
  --host <target> \
  --port 3211 \
  --hostname app.preferences.wisent.com \
  --consumer preferences-web \
  --database preferences \
  --database-field pooler_url \
  --database-variable DATABASE_URL \
  --secret PREFERENCES_OPERATOR_TOKEN=preferences-operator-api#token \
  --env NEXT_PUBLIC_BASE_URL=https://app.preferences.wisent.com \
  --readyz /api/readyz

stado web list shows every declared product with its host, port, hostname and unit. stado web remove stops the unit, forgets it, and removes the hostname's record.

Source: this website