Provisions the wardnet cloud — three mesh-mTLS services (tenants, ddns, tunneller)
on two hosts per region — using inforge (v2.2.0+),
a Go toolchain that turns declarative YAML resource definitions into cloud deployments
via Pulumi + GitHub Actions. There is no Pulumi program to maintain: infrastructure is
declared as YAML under resources/<env>/. To change infrastructure, edit YAML only.
See AGENTS.md for the full topology, directory map, and conventions.
inforge validate prd # static validation (credential-free)
inforge preview prd --report report.md # dry-run plan (needs provider creds in env)| Workflow | Trigger | Action |
|---|---|---|
validate.yml |
PR | inforge validate prd |
preview.yml |
PR | inforge preview prd → posts the report as a PR comment |
deploy.yml |
Push to main |
inforge deploy prd --yes then inforge pki renew prd |
reconcile.yml |
Nightly 03:00 UTC | re-assert prd + inforge pki renew prd (mesh-leaf rotation) |
deploy-raw-service.yml |
repository_dispatch / manual | inforge releases push + deploy for one service |
Secrets fall into two categories. Keep them separate.
Values the deploy tooling itself needs (cloud providers, state/artifact backends, the
age master identity, the deploy SSH key). Set as repository Actions secrets; each
workflow maps them into its env: block.
| Secret | Purpose |
|---|---|
HCLOUD_TOKEN |
Hetzner Cloud API |
CLOUDFLARE_API_TOKEN / CLOUDFLARE_ZONE_ID |
DNS authority + ddns's own DNS work |
CLOUDFLARE_ACCOUNT_ID / AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / PULUMI_CONFIG_PASSPHRASE |
R2 state + release-artifact backends (S3-compatible) |
NEON_API_KEY |
Neon database API |
INFISICAL_CLIENT_ID / INFISICAL_CLIENT_SECRET |
Infisical machine identity (secrets provider) |
INFORGE_SECRETS_KEY |
CI master age identity — mints mesh leaves, decrypts the PKI + secret stores |
SSH_AUTHORIZED_KEYS / DEPLOY_PUBLIC_KEY / DEPLOY_PRIVATE_KEY |
human SSH + deploy user (private key realizes services over SSH) |
SERVICE_ARTIFACTS_TOKEN |
(optional) read access to release tarballs on private repos (deploy-raw-service.yml only) |
Values a service consumes at runtime — inforge tooling never reads them, so they do
not belong as CI secrets. They live in the git-committed, age-encrypted secret store
(resources/prd/secrets.enc.yaml), referenced from a service's environment.yaml with a
vault: source. Only INFORGE_SECRETS_KEY decrypts the store; the deploy projects each
value to the secrets provider.
| Secret (vault key) | Service | Set with |
|---|---|---|
STRIPE_SECRET_KEY |
tenants | inforge secret set prd tenants STRIPE_SECRET_KEY |
STRIPE_WEBHOOK_SECRET |
tenants | inforge secret set prd tenants STRIPE_WEBHOOK_SECRET |
Values are read from stdin and never displayed. Commit the updated secrets.enc.yaml.
INFORGE_PKI_ROOT_KEY (the two-tier mesh root identity printed by inforge pki init)
signs intermediates offline and must never enter CI or the repo. Keep it in an
offline operator vault.
- Never modify multiple environments in one PR.
- Never commit plaintext secrets. Use
${ENV_VAR}/env:/vault:; real values live in GitHub Actions secrets or the age-encrypted store. - Never edit the Pulumi state in R2 by hand.
- Always run
inforge validate prdbefore pushing.