Prompt
How should I set up secrets, state, and approvals for infrastructure changes in Git?
Latest observation
A solid Git-based infrastructure setup usually follows three rules:
- No long-lived secrets in Git
- State is stored remotely, not in the repo
- Changes require explicit review/approval before apply
Here’s a practical way to set it up.
1) Secrets: keep them out of Git entirely
Good patterns
- Use your cloud/provider secret manager
- AWS Secrets Manager / SSM Parameter Store
- Azure Key Vault
- GCP Secret Manager
- HashiCorp Vault
- Inject secrets at runtime
- CI/CD reads secrets from the secret store
- Terraform/Ansible/etc. get them as environment variables or data-source lookups
- Use short-lived credentials
- Prefer OIDC federation from GitHub Actions/GitLab CI to cloud roles
- Avoid static access keys in CI
In the repo
- Commit only:
*.examplefiles- encrypted secret references/config
- non-sensitive variables with placeholders
If you must keep secrets in Git
Use encryption tools:
- SOPS + KMS/age/Vault
- Sealed Secrets for Kubernetes
- git-crypt in limited cases
But even then, keep decryption keys outside the repo and limit access.
2) State: use remote state with locking
For infrastructure-as-code tools like Terraform, Pulumi, etc., do not store state in Git.
Terraform example
Use a remote backend such as:
- S3 + DynamoDB lock table on AWS
- Terraform Cloud/Enterprise
- GCS
- Azure Blob Storage
Why
- Prevents state drift and merge conflicts
- Enables collaboration
- Supports locking to avoid simultaneous applies
- Keeps sensitive values out of local files and commits
Best practices
- Enable state locking
- Encrypt state at rest
- Restrict access with least privilege
- Separate state per environment:
devstagingprod
- Keep workspace/environment boundaries clear to avoid accidental cross-env changes
Important
Even remote state can contain secrets depending on your IaC tool and resources. Treat state as sensitive and lock down access accordingly.
3) Approvals: use PR-based change control
Recommended workflow
- Developer creates a branch
- Makes IaC changes
- Opens a Pull Request / Merge Request
- Automated checks run:
- fmt/lint
- validate
- plan/diff
- policy checks
- Human review approves
- Merge triggers deploy/apply, or apply is a separate manual step
For production
Add stronger controls:
- Require at least 1–2 reviewers
- Require approval from infra/security owners
- Require passing policy checks
- Require manual approval for apply
- Use protected branches/environments
Split plan and apply
A strong pattern is:
- PR generates plan
- Merge does not auto-apply
- Apply requires separate approval or a gated manual workflow
This reduces the risk of a bad merge immediately changing production.
4) A practical policy model
Dev environment
- Auto-plan on PR
- Auto-apply on merge may be acceptable
- Lower approval threshold
Staging
- PR plan required
- At least one review required
- Apply after merge, maybe with manual confirmation
Prod
- PR plan required
- Two-person review or code-owner approval
- Manual approval gate before apply
- Restricted deploy permissions
- Change window if needed
5) Repository structure suggestions
Example:
infra/
modules/
envs/
dev/
staging/
prod/
Or separate repos if you need stricter isolation:
infra-modulesinfra-live-devinfra-live-prod
For higher security/compliance, separating “modules” from “live environment configs” is useful.
6) Access control and auditability
Limit who can:
- read secrets
- modify state
- approve prod changes
- run apply jobs
- access cloud credentials
Turn on audit logs
- Git provider audit logs
- CI/CD logs
- Cloud provider audit trails
- Secret manager access logs
This gives you traceability from: commit → PR → approval → apply → cloud change
7) A secure Terraform workflow example
On PR
- CI authenticates with OIDC
- Runs
terraform fmt,validate,plan - Posts the plan to the PR
- No secrets stored in repo
- No apply permissions in PR pipeline
On merge to main
- Protected branch ensures review approval
- CI may still require a manual approval for prod apply
- Uses remote backend with locking
- Fetches secrets from secret manager at runtime
8) Common mistakes to avoid
- Committing
.tfstateor.tfvarswith secrets - Using the same credentials for all environments
- Giving CI static cloud keys
- Auto-applying prod changes on merge without approval
- Mixing dev and prod state
- Letting too many people approve or apply prod changes
- Keeping secrets in plain environment variables in logs
A simple recommended baseline
If you want a safe default:
- Secrets: store in cloud secret manager or Vault; use OIDC for CI
- State: remote backend with encryption and locking
- Approvals: PR review + protected branches + manual prod approval
- Environments: separate state and permissions per env
- Auditing: enable logs everywhere
If you want, I can also give you:
- a Terraform-specific setup for AWS/GCP/Azure, or
- a GitHub Actions / GitLab CI example with plan/apply approvals.