We have a team of about 10 developers, and Terraform manages our infrastructure. The one exception is Pulumi, which connects through a bastion host to create users in PostgreSQL on RDS and MongoDB-compatible DocumentDB.
We now need to use AWS Secrets Manager to store database passwords and let developers retrieve the credentials through IAM policies. IAM database authentication is not an option, so credentials must be managed through Secrets Manager or another password-management system.
Keeping Terraform and Pulumi state for what is essentially database-user provisioning feels unnecessarily complicated. How do you manage RDS and DocumentDB users and credentials in practice, and what pattern would let us remove Pulumi while keeping the process secure, repeatable, and easy to rotate?
3 Answers
Keep Terraform as the source of truth for the databases, IAM permissions, Secrets Manager entries, and the execution role. Handle database users with one versioned, idempotent bootstrap or migration job instead of treating them as long-lived Terraform resources. The job can run inside the VPC as an ECS task, CodeBuild job, or Kubernetes Job, use psql and mongosh, read an administrator secret, create or alter users, and write each user’s credentials directly to Secrets Manager. Terraform can trigger it after the databases are ready, without maintaining a second state engine. The same job, or a native rotation Lambda, can update both the database passwords and the stored secrets. If network access through a bastion is required, SSM port forwarding is generally cleaner than exposing a bastion host.
Another option is to use a secrets platform such as OpenBao as the central credential manager. It can generate, store, and rotate database credentials while applications and developers receive access through policies. This may be useful if you want database credential lifecycle management without putting the logic into either Terraform or Pulumi, although it introduces another service to operate.
You can also have Terraform generate credentials and invoke a provisioning Lambda for each required user. Store the generated values in a protected parameter store or Secrets Manager, and have the Lambda retrieve the password and create or update the corresponding account. A for_each-style setup can invoke the function once per user, with an explicit dependency ensuring the secret exists first. This keeps orchestration in Terraform, but be careful because generated passwords or Lambda inputs can still end up in Terraform state depending on the resources used. A job that generates credentials inside the runtime and writes them directly to Secrets Manager avoids more of that exposure.

That makes sense. Keeping the user lifecycle in one idempotent script would avoid duplicating ownership between Terraform and Pulumi while still letting Terraform manage access and secrets.