Terraform security practices: sensitive variables, secret management, state protection, .gitignore patterns, and CI/CD credential handling. Trigger: When handling secrets in Terraform, configuring state backends, reviewing .gitignore for Terraform, or setting up CI/CD pipelines for infrastructure.
.gitignore for a Terraform directoryterraform plan/apply| File/Pattern | Why | Consequence if Leaked |
|---|---|---|
*.tfvars | Contains actual secret values | Full credential exposure |
*.tfstate / *.tfstate.backup | Contains ALL resource attributes including secrets in plaintext | Infrastructure takeover |
.terraform/ |
| Contains provider binaries and cached state |
| Potential secret exposure |
These MUST be in .gitignore. No exceptions. Ever.
See assets/gitignore-terraform for the complete
.gitignorepattern.
ANY variable that holds a secret MUST be marked sensitive = true. This prevents Terraform from displaying the value in plan/apply output and logs.
variable "discord_token" {
description = "Discord bot token"
type = string
sensitive = true
}
Terraform will show (sensitive value) instead of the actual value in all output.
| Method | When to Use | Security Level |
|---|---|---|
TF_VAR_ environment variable | CI/CD pipelines, automation | High — never touches disk |
| Secret manager (GCP Secret Manager, Vault) | Production infrastructure | Highest — encrypted, audited, rotated |
terraform.tfvars file (local only) | Local development only | Medium — on disk but gitignored |
-var CLI flag | One-off operations | Low — visible in shell history |
Hardcoded in .tf files | NEVER | Catastrophic — in git forever |
See assets/secret-passing-examples.sh for the patterns.
The Terraform state file contains the full truth about your infrastructure — including every attribute of every resource. For many providers, this includes secrets in plaintext.
Local state (default):
terraform.tfstate in the working directoryRemote state (recommended for teams):
See assets/remote-backend-gcs.tf for the GCS backend configuration.
When running Terraform in CI/CD (GitHub Actions, Cloud Build):
| Rule | Why |
|---|---|
| Secrets via environment variables only | Never pass via CLI flags (visible in logs) |
Use TF_VAR_ prefix for Terraform variables | Standard Terraform convention |
| Store secrets in GitHub Secrets / GCP Secret Manager | Encrypted at rest, access-controlled |
Run plan on PRs, apply only on merge to main | Prevent accidental infrastructure changes |
Use -input=false flag | Prevent interactive prompts in CI |
Use -no-color flag | Clean log output |
NEVER log terraform output with sensitive values | Sensitive outputs leak in CI logs |
See assets/github-actions-terraform.yml for the GitHub Actions workflow.
| Provider | Auth Method | Environment Variable |
|---|---|---|
| GCP | Service account key or Workload Identity | GOOGLE_APPLICATION_CREDENTIALS or GOOGLE_CLOUD_PROJECT |
| Discord | Bot token | TF_VAR_discord_token |
| AWS | Access key or IAM role | AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY |
Prefer Workload Identity Federation over service account keys when possible — no long-lived credentials.
Adding a secret to Terraform? → variable with sensitive = true, pass via TF_VAR_
Working alone on non-prod? → Local state is fine (gitignored)
Working in a team? → Remote backend with locking (GCS/S3)
Setting up CI/CD? → GitHub Secrets + TF_VAR_ + plan on PR, apply on merge
Reviewing a PR with .tf changes? → Check for hardcoded secrets, missing sensitive flags
State file accidentally committed? → ROTATE ALL CREDENTIALS IMMEDIATELY, then remove from git history
| File | Description |
|---|---|
assets/gitignore-terraform | Complete .gitignore pattern for Terraform directories |
assets/secret-passing-examples.sh | How to pass secrets via environment variables and tfvars |
assets/remote-backend-gcs.tf | GCS remote backend configuration with state locking |
assets/github-actions-terraform.yml | GitHub Actions workflow for plan on PR, apply on merge |
terraform plan -input=false # Non-interactive plan (for CI)
terraform apply -input=false # Non-interactive apply (for CI)
| Don't | Do |
|---|---|
Hardcode secrets in .tf files | Use sensitive variables + TF_VAR_ env vars |
Commit *.tfvars with real values | Commit only *.tfvars.example with placeholder values |
Commit *.tfstate | Add to .gitignore, use remote backend for teams |
Pass secrets via -var flag | Use TF_VAR_ environment variables |
Run apply in CI on every push | Run plan on PRs, apply only on merge to main |
| Use service account keys in CI | Use Workload Identity Federation |
| Share state files via Slack/email | Use a remote backend with IAM access control |
| Ignore leaked state file | ROTATE ALL CREDENTIALS immediately |