Docs
Install

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).

FilePurpose
licence.jsonThe licence (see Licensing)
connectors.jsonThe tools 3AM connects to
policy.jsonAutonomy defaults, rules, escalation, budgets
estate-overrides.jsonCorrections to the discovered service map
packs/*.3pkSealed 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:

FormMeaning
{"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
}
KeyMeaning
default_autonomyshadow (recommended) or off. Services are raised to L1 one by one, with approval, never by file
approval_chainChannels in order; after_s is when to escalate to that channel
autonomy_changeWho must approve raising a service's autonomy: quorum (default 2), channels, and expiry
rulesFirst match wins. Match on action, risk, reversible, environment, service, connector. Then deny with a reason, a quorum, and allowed hours
budgetsMost actions per service per hour, and in total
servicesPer-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"]}}}}

On this page