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 andstado 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