the stack file
A stack is a single-document YAML file with an apiVersion and kind
so the schema can evolve without breaking older files. pngr.dev/v1 is
the only version today.
complete example
apiVersion: pngr.dev/v1
kind: Stack
metadata:
org: acme # org slug — must match the auth context's org
spec:
channels:
- name: ops-alerts # name is the upsert key; unique per (org, kind)
type: slack
enabled: true
config:
webhook_url: "${SLACK_OPS_WEBHOOK}" # ${VAR} expanded from env at apply time
- name: oncall-email
type: email
config:
recipients: ["[email protected]"]
monitors:
- name: marketing-site
url: https://acme.dev
check_type: http
interval_seconds: 60
timeout_seconds: 10
expected_status_codes: [200, 301, 302]
tags: [public, marketing]
- name: api
url: https://api.acme.dev/healthz
check_type: http
interval_seconds: 30
expected_body_substring: "ok"
rules:
- name: prod-pager
trigger: monitor_down
failure_threshold: 2
renotify_minutes: 30
applies_to:
monitors: [marketing-site, api] # by name; backend resolves to ids
escalations:
- level: 1
channels: [ops-alerts]
- level: 2
delay_minutes: 10
channels: [oncall-email]
field conventions
nameis the upsert key for every resource —(org, kind, name)is unique. Renaming a resource in the file is treated as delete + create unless you set an explicitprevious_name.- Cross-references use
name, neverid. A rule'sapplies_to.monitorsandchannelslists reference resources by name; the apply pipeline resolves names to ids server-side. If a referenced name doesn't exist in the same file, apply fails with one readable error listing every unresolved name. - Fields mirror the REST API (snake_case, to match the API
reference), so a
monitors[]block round-trips to and from the monitors endpoint.
secrets
Credential fields support ${ENV_VAR} interpolation, expanded at apply
time:
config:
webhook_url: "${SLACK_OPS_WEBHOOK}"
The unrendered file is safe to commit — interpolation happens in CI
with secrets injected from your secret store. A dump
never writes secrets in plaintext; it emits ${SECRET_NAME} placeholders
derived from the channel name and field, which you wire up to real values
on your side.
One file, one org. A stack file targets a single org (metadata.org).
Multi-org files aren't supported. Templating beyond ${ENV_VAR} is out
of scope too — no Go templates, Jsonnet, or Helm-style values. If you
need that, generate the YAML upstream and feed the result to apply.
next
- apply, diff & prune — reconcile the file.
- dump & round-trip — export the live org back to YAML.