production. Before starting, prerequisites.md
is done and the inputs I1 to I6, I13, I14 and I15 exist (I8 for monitoring delivery).
Status: never run against a cloud account (the inputs do not exist yet, and the core role
refuses PostgreSQL below 18.7 outside the local converge). The same playbooks ran end to end on
the local converge on 2026-11-12 and 2026-11-13 (local-converge.md
sections 3 and 5). Expected outputs quoted from those runs say so.
Steps
-
Secrets. Follow secrets.md section 5: recipients,
.sops.yaml,infra/secrets/staging/secrets.enc.yamlandinfra/secrets/staging/tofu.enc.env. Check:sops decrypt infra/secrets/staging/secrets.enc.yaml | grep -c ':'prints a number and no error. -
Infrastructure. Follow opentofu.md section 6:
terraform.tfvars,make state-bootstrap ENV=staging,make staging-plan,make staging-apply. Check:infra/ansible/inventories/staging/hosts.jsonexists with three edge and two core hosts, andssh ops@<edge 1 address> truesucceeds. -
DNS. Check that Cloudflare answers and points at Traffic Manager:
Expected: Cloudflare’s name servers, then the CNAME to
<domain>-<env>-in.trafficmanager.netfollowed by the three edge addresses. Before the first converge no node answers port 80, so every endpoint isDegradedand Traffic Manager returns all of them, which is its documented behaviour when every endpoint fails. -
Non-secret Ansible variables. Write
infra/ansible/inventories/staging/group_vars/all.ymlas in ansible.md section 8 step 1 (sa_image_registry,sa_operator_github_ids);sa_tls: cloudflareandsa_public_bind_ipv4are already there. -
Release. A tag
v<release>on the commit to deploy runsrelease-images.yml, which builds, scans, signs and attests the eleven platform images and attachesrelease-images.json(../security/supply-chain.md). Downloadrelease-images.jsonfrom the release. (GitHub Actions fails at start-up on the owner’s account today; until that is fixed no signed release exists.) -
First converge:
What happens, in order (
infra/ansible/playbooks/site.yml): the base role (users, SSH hardening, host firewall, chrony with an offset check, Docker at its pins) and the secrets role on every node; NATS and etcd on the edge nodes; postgres-ha (Patroni) and HAProxy on the core nodes; the pgBackRest settings and schedules;bootstrap.ymlon the Patroni leader; the edge and core services; the observability role on edge and core nodes.bootstrap.yml(once, idempotent on every later run): checkssa_operator_github_ids(input I3), waits for a writable leader, creates the Umami role and database and the platform database,platformctl db migrate,platformctl db roles, starts Umami (which migrates its own database),platformctl umami bootstrap(replaces the seeded admin password),platformctl db umami-retention-role,platformctl nats apply. Expected:PLAY RECAPwithfailed=0everywhere. On the local converge the first run took 177 s with images already in the registry. -
Second converge (same command). Expected:
changed=0on every node. -
Certificates. Each node’s Caddy asks Let’s Encrypt for its names through DNS-01. Check
each node as in ../runbooks/certificates.md step 1. Expected:
issuer Let’s Encrypt for
in.,otlp.,app.andweb.<input I4>. -
First operator sign-in. An operator listed in I3 opens
https://app.<input I4>/and signs in with GitHub (OAuth App of input I2, callbackhttps://app.<input I4>/api/auth/github/callback). Expected: the console shows the operator area. -
Probe tenant (monitoring, plan phase 17). On the core node that holds the Patroni leader,
with the operator of step 9:
It prints
tenant_id=,site_id=,otlp_token=andmachine_api_key=once. Putsite_id,otlp_tokenandmachine_api_keyinto the SOPS file asprobe_site_id,probe_otlp_tokenandprobe_machine_api_key(sops --config infra/secrets/.sops.yaml edit infra/secrets/staging/secrets.enc.yaml). Then restartjobson both core nodes (sudo docker compose restart jobsin/opt/veristar/core): a new jobs leader runs every task at once, including the reconcile that provisions the probe’s Umami websites and registry entries. -
Probe. The prober runs outside Azure as a container, on the owner’s home server beside
Uptime Kuma and ntfy: render the bundle and follow probe.md. It needs the probe
tenant’s values of step 10 in the SOPS file. Expected: every Kuma monitor of probe.md
section 4 is up within a few minutes. A probe host in the inventory’s
probegroup (with its own public address) would instead getsite.yml’s “Probe” play, with Caddy forstatus.<input I4>and the Alertmanager watchdog;make ops-localexercises that variant locally. -
SLO collection. Set the repository variable
STATUS_BASE_URLtohttps://status.<input I4>soslo-report.ymlcan collect (../runbooks/slo-report.md). - Verify with verification.md.