This page provisions the agent side of Database Monitoring for PostgreSQL with Terraform: an EC2 host running the Atatus Infrastructure Agent, the IAM policy that lets it authenticate, and the security group rule that lets it reach your database. The same stack can also monitor Amazon DocumentDB from that one host. See Set Up the Agent with Terraform for DocumentDB for the DocumentDB half, and Monitoring both engines below for running them together.

Before you begin

Terraform reads AWS credentials from the environment, so select a profile with AWS_PROFILE rather than a command line flag. If AWS_ACCESS_KEY_ID is exported it takes priority over AWS_PROFILE, so run unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN first.

Two prerequisites Terraform will not change for you

Both carry consequences for a running database, so they stay yours to make. Terraform checks them and tells you, and never edits them.

IAM database authentication

Required for the agent to authenticate without a password. It is a dynamic change and needs no reboot:
For an Aurora cluster the setting lives on the cluster:
--apply-immediately applies every pending modification on that database, not only this one. Anything queued for the next maintenance window is applied now too, and some of those changes reboot the instance. Check what is pending first with aws rds describe-db-instances --db-instance-identifier <ID> --query 'DBInstances[0].PendingModifiedValues', or leave the flag off and let this apply in the next window.
Terraform warns at plan time when this is off on an Aurora cluster. For a DB instance it warns that it cannot tell, because the aws_db_instance data source does not expose the flag at all. Check it yourself:

The parameter group

Database Monitoring reads its query data from pg_stat_statements, and a few parameters control how much of it you get. Set these in your RDS or Aurora parameter group, or in postgresql.conf on a self hosted server: The two marked static do nothing until the database is restarted. The rest take effect on the next parameter group refresh. Check what is actually running:
Many recent default parameter groups already include pg_stat_statements, so check before you plan any downtime.
If you do need to change it, that reboot restarts your database, not the agent. Every open connection is dropped while it comes back, usually for a minute or two. On Multi AZ it fails over to the standby unless you pass --no-force-failover. On Aurora, reboot the writer, which triggers a failover. Do it in a maintenance window.
This is the failure that looks like success. CREATE EXTENSION pg_stat_statements succeeds whether or not the library is loaded, so a parameter group edited without a reboot gives you an agent that connects, reports metrics, and shows an empty query list, with nothing saying why. The agent host checks the running value at boot and writes a notice to its journal if it is missing.

Get the Terraform

These are examples to copy into your own repository and adapt. There is no registry entry and no version to pin, so fork the directory rather than depending on it.

Store the license key

Keeping the key in Parameter Store means it never lands in user_data, in a saved plan file, or in state:

Configure

A PostgreSQL only setup, with documentdb_clusters left empty:
If you do not know your VPC, subnet or security group, read them off the database:
The agent host needs outbound access to download the agent and reach Atatus. A private subnet needs a NAT gateway. A public subnet with only an internet gateway needs a public address, so set associate_public_ip_address = true. With neither, the host boots, fails to install, and never retries, because its boot script runs only once.

Monitoring several databases

databases is a map, and one agent host collects from every entry. There is no reason to run this stack twice for two databases in the same VPC:
Each database gets its own entry in the rendered config with its own aws block, so each token is signed for its own endpoint, and its own rds-db:connect policy, because the resource id differs per database. Databases that share a security group and port share one ingress rule. The map key names the database in the UI, which matters once one agent reports several of them.

Create the database role

The agent authenticates as a PostgreSQL role that has to exist before it starts. There are two ways to create it.

Automatically, at first boot

Point the entry at an SSM parameter holding the database master password and the agent host creates the role itself. The host is inside the VPC, so it reaches a database with no public access, which your laptop cannot:
The host runs CREATE ROLE, GRANT pg_monitor, CREATE EXTENSION pg_stat_statements, GRANT rds_iam, and creates the atatus schema and the atatus.explain_statement() function that execution plans need. Every statement is guarded, so a rebuilt host re-runs all of it without error, and the rds_iam grant is skipped on a server that has no such role.
The master password is a real credential. It is read at boot, used once, and never written to disk, but the host holds it in memory while it runs. If that is not acceptable in your environment, create the role by hand instead. This is a convenience, not a security improvement.

By hand

Connect as your master user and run the bootstrap SQL once per cluster:
GRANT rds_iam is a one way switch for the role it is applied to. Once a role holds it, that role can never authenticate with a password again, and the only way back is to drop and recreate it. Never grant it to your master user.
That script also creates the atatus schema and the atatus.explain_statement() function, which is what the agent calls to collect execution plans. Both are per database, so run it in every database you want plans from.
Without that function you get query metrics but no execution plans, and the UI reports schema "atatus" does not exist. The automatic path above creates it for you in the database it connects to; for additional databases in the same cluster, run this in each one:
If your database has no public access, your laptop cannot reach it. Tunnel through the agent host with Session Manager, or use the automatic path above:

Apply

Verify

Run the session command to open a shell on the host, then paste the verification command inside it. It checks that the agent is running, reports the installed version, shows the integration in the logs, and mints a token to prove the host can log in. To read the boot script’s own account of what happened:

Monitoring both engines from one host

The agent reads conf.d/postgresql.d/ and conf.d/mongodb.d/ independently, so one machine covers PostgreSQL and Amazon DocumentDB together. Add a documentdb_clusters map alongside databases:
There is no second agent, no second host and no second license. At least one of the two maps must be non empty; a plan time precondition rejects a configuration that would build a host monitoring nothing. The DocumentDB side has its own prerequisites, and its IAM works by a different mechanism. See Set Up the Agent with Terraform for DocumentDB.

Removing it

Your database is untouched. The only change to it was an ingress rule on its security group, which is removed. The atatus role and the SSM parameters are not Terraform’s and stay behind:
Keep the role if you plan to redeploy. It holds rds_iam, dropping it is the only way to undo that grant, and you would have to recreate and re grant on the next run.