Secrets in IaC: SOPS & Vault
Keep credentials out of state and code.
IaC has a specific, dangerous secrets problem: the code and the state both routinely end up holding credentials in plaintext. A database password in a .tf variable, a provider API key hardcoded, or — the sneaky one — a secret that Terraform writes into its state file as a resource attribute. The rule from every other course applies with force here: secrets must never be committed to the repo or left readable in state. Solving it means both keeping secrets out of the code and protecting the state that may capture them anyway.
Fetch secrets at runtime, do not store them
The strongest approach is to never put the secret in the code at all — fetch it at apply time from a secrets manager. Terraform’s Vault provider (and the cloud secrets-manager data sources) pull a credential from Vault or AWS/GCP Secrets Manager during the run, so the code references "the database password in Vault" rather than the value. Even better, authenticate the pipeline to the cloud via OIDC short-lived credentials (the CI lesson’s pattern) so there is no long-lived cloud key stored anywhere either. The secret lives in one managed place; the code just points at it.
# reference a secret from Vault instead of hardcoding it:data "vault_kv_secret_v2" "db" { mount = "secret", name = "prod/db" }resource "aws_db_instance" "main" {password = data.vault_kv_secret_v2.db.data["password"] # fetched at apply, not in code}# and encrypt any secrets that must live in a file, with SOPS:$ sops --encrypt --age $AGE_KEY secrets.dec.yaml > secrets.enc.yaml # commit the ENCRYPTED file
SOPS and protecting state
When a secret genuinely must live in a file you commit (some config values), SOPS (Secrets OPerationS) encrypts just the values — you commit an encrypted file, and it is decrypted only at use with a key from KMS/age/Vault, so the repo never holds plaintext. And because Terraform may still capture some secrets in state regardless of how carefully you fetch them, the second front is non-negotiable: a remote backend that encrypts state at rest, with tight IAM so only the pipeline and the right people can read it. You minimize what reaches state, and you protect the state that does.