variables.yaml
Per-environment variables file at resources/<env>/variables.yaml. It holds the base domain and SSH
config for the environment. Which regions deploy and all provider config (credentials + per-region
realizations) live in regions.yaml.
Schema
base_domain: example.com # required — base domain for VM DNS names
ssh: # required when using compute
authorizedKeys: "ssh-ed25519 AAAA..." # user access SSH public key(s)
deployPublicKey: "ssh-ed25519 AAAA..." # deploy user's public key
observability: # optional — Grafana Cloud integration
otlp_endpoint: https://otlp-gateway-....grafana.net/otlp # host/DB metrics collector
grafana_url: https://myorg.grafana.net # dashboards + alerts target
security: # optional — edge security tier (public ingress + gateway hosts)
crowdsec:
enabled: true # IP banning (nftables) + free community blocklist on edges
rate_limit:
enabled: true # one blanket IP-based limit on every public edge server
requests_per_second: 20
burst: 40
max_connections: 40
Fields
base_domain
The root domain for DNS names. A host's domain is assembled as
<compute>.vm.<env>.<region-slug>.<base_domain> (e.g. bridge.vm.prd.use1.wardnet.network), and a
service's as <service>.svc.<env>.<region-slug>.<base_domain>.
ssh
SSH keys placed on every provisioned VM:
authorizedKeys— added to the VM's authorized_keys for human (admin) access.deployPublicKey— the SSH public key installed for the deploy user. When a compute resource declares adeploy_user, inforge provisions that account at VM-init time and installs this key into itsauthorized_keys. The username itself is set per-compute indeploy_user.name; the key material lives here so that rotating it only requires updatingvariables.yamland re-runninginforge deploy.
observability
Optional Grafana Cloud integration. Both fields are non-secret URLs; their credentials
are reserved secrets in secrets.enc.yaml, never committed here.
otlp_endpoint— the OTLP/HTTP base URL for the host VM-metrics and Postgres-metrics collector (ADR-0031/0037). When set, inforge installs the collector on every VM; its Basic-auth credential is the reserved secretobservability/otlp_auth(inforge secret set <env> observability otlp_auth --reserved). Empty ⇒ no collector.grafana_url— the base URL of the Grafana instance inforge pushes the built-in dashboards (and, in later slices, alerts) to (ADR-0038). When set,inforge deployrealizes this env's dashboards under aninforge / <env>folder, prefixed by env so multiple environments can share one Grafana org. Its service-account token is the reserved secretobservability/grafana_token(inforge secret set <env> observability grafana_token --reserved). Agrafana_urlset with no token fails the deploy. Empty ⇒ no dashboards are managed.built_in_dashboards— whether inforge manages the generated Infrastructure/Database/ Service dashboards for this env. Defaulttrue; setfalseto opt out (custom dashboards are unaffected).built_in_alerts— whether inforge manages the generated alert rules for this env. Defaulttrue; setfalseto opt out (custom alerts are unaffected).default_profile— the notification profile (fromobservability/notifications.yaml) that built-in alerts and any alert omittingprofile:route through. Required once alerts are managed.
security
Optional edge security tier applied to the public ingress and
gateway hosts. Off unless enabled. An individual ingress or gateway
opts the whole tier out with security: false on its own spec.
-
rate_limit— a single blanket, IP-based rate limit applied uniformly to every public server on every edge. It is a security floor, not per-route tuning: the same limit covers all routes, apps, and gateway paths (per-route / per-identity limits are a future gateway concern). Requests over the limit are answered429. Fields:enabled— turn rate limiting on (default off).requests_per_second— sustained per-client-IP request rate (limit_req).burst— how many excess requests may queue before a429is returned.max_connections— concurrent connections allowed per client IP (limit_conn).
Health-check and ACME (certificate) endpoints are never rate-limited.
-
crowdsec— installs the CrowdSec agent + nftables firewall bouncer on every edge host. It parses the ingress nginx logs, bans abusive and known-bad IPs at the kernel (nftables), and pulls the free crowd-sourced community blocklist for pre-emptive protection. Fields:enabled— turn CrowdSec on (default off).version— optional pin for thecrowdsecagent package. The firewall bouncer versions independently, so it is never pinned; omit to install the repo's current pair.console— enroll the host in the CrowdSec console dashboard. Requires the reserved secretsecurity/crowdsec_enroll(inforge secret set <env> security crowdsec_enroll --reserved). The community blocklist itself needs no secret.
When
observability.otlp_endpointis set, CrowdSec's own metrics (log-acquisition rate, active decisions, bouncer pulls) are scraped by the host collector and shipped to Grafana Cloud alongside host and database metrics — so you can see it working, and catch its silent-failure mode (unreadable logs ⇒ nothing parsed). A deploy fails if CrowdSec does not come up on an edge host.
Example
base_domain: example.com
ssh:
authorizedKeys: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@laptop"
deployPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... deploy@ci"
observability:
otlp_endpoint: https://otlp-gateway-prod-eu-west-2.grafana.net/otlp
grafana_url: https://wardnet.grafana.net
default_profile: prod # notification profile for built-in + un-profiled alerts
# built_in_dashboards: false # opt out of the generated dashboards
# built_in_alerts: false # opt out of the generated alert rules
security:
crowdsec:
enabled: true
# console: true # dashboard enrollment (needs security/crowdsec_enroll)
rate_limit:
enabled: true
requests_per_second: 20
burst: 40
max_connections: 40