Configuration files
Manage 3AM as code: the files in /etc/3am, and how secrets are referenced.
Everything can be set up in the console. If you prefer to manage 3AM as code (GitOps, Ansible, a ConfigMap), put
these files in a directory and mount it read-only at /etc/3am (./install.sh --config DIR, or the Helm
values config.existingConfigMap, licence.existingSecret and packs.existingSecret).
| File | Purpose |
|---|---|
licence.json | The licence (see Licensing) |
connectors.json | The tools 3AM connects to |
policy.json | Autonomy defaults, rules, escalation, budgets |
estate-overrides.json | Corrections to the discovered service map |
packs/*.3pk | Sealed check packs |
Who wins. A connector or policy defined in a file wins over one with the same name in the console, and the console shows it as managed in a file (read-only). Console-managed settings live in the data volume.
connectors.json
{
"connectors": [
{"name": "prometheus", "kind": "prometheus",
"settings": {"url": "https://prometheus.bank.internal:9090", "alertmanager_url": "https://alertmanager.bank.internal:9093",
"bearer_token": {"file": "/etc/3am/secrets/prometheus/token"}, "ca_file": "/etc/3am/ca/bank-ca.pem"}},
{"name": "pagerduty", "kind": "pagerduty",
"settings": {"api_token": {"env": "PD_API_TOKEN"}, "from_email": "[email protected]",
"watch_services": ["PXXXXXX"], "service_id": "PYYYYYY", "approvers": ["[email protected]"]}}
]
}Each connector's settings are listed on its page under Connectors.
Secrets are references, never values
A secret setting must be one of:
| Form | Meaning |
|---|---|
{"file": "/etc/3am/secrets/…"} | Read from a file, such as a Kubernetes Secret or Docker secret mounted into the container |
{"env": "NAME"} | Read from an environment variable of the 3AM container |
A secret written inline is refused at start-up: it would otherwise end up in backups, diffs and support bundles.
policy.json
{
"default_autonomy": "shadow",
"approval_chain": [{"channel": "slack"}, {"channel": "pagerduty", "after_s": 300}, {"channel": "phone", "after_s": 900}],
"autonomy_change": {"quorum": 2, "chain": [{"channel": "slack"}], "expires_s": 3600},
"rules": [
{"match": {"action": "mysql.start"}, "deny": true, "reason": "database restarts go through the DBA team"},
{"match": {"risk": "high"}, "quorum": 2, "hours": "07:00-22:00"},
{"match": {"reversible": false}, "quorum": 2}
],
"budgets": {"per_service_per_hour": 3, "global_per_hour": 10},
"services": {"core-banking-db": {"environment": "prod", "approvers": {"slack": ["U02ABCDEF"]}}},
"utc_offset_minutes": 0
}| Key | Meaning |
|---|---|
default_autonomy | shadow (recommended) or off. Services are raised to L1 one by one, with approval, never by file |
approval_chain | Channels in order; after_s is when to escalate to that channel |
autonomy_change | Who must approve raising a service's autonomy: quorum (default 2), channels, and expiry |
rules | First match wins. Match on action, risk, reversible, environment, service, connector. Then deny with a reason, a quorum, and allowed hours |
budgets | Most actions per service per hour, and in total |
services | Per-service environment and approvers per channel |
See How 3AM decides → Safety rails for how these apply.
estate-overrides.json
When discovery links something wrongly, correct it here. Overrides always win:
{"services": {"payments": {"repos": ["bank/payments-service"], "owner": "[email protected]",
"team": "Payments", "criticality": "1", "approvers": {"slack": ["U02ABCDEF"]}}}}