A reliable Terraform CI/CD workflow turns infrastructure changes into reviewable, repeatable deployments. This checklist explains how to structure the repository, validate changes, protect state and secrets, review plans, approve applies, and detect drift without making the pipeline harder to operate than the infrastructure itself.
Overview
Terraform is most useful when the configuration, execution process, and resulting state are managed together. Running terraform apply from a developer laptop may be acceptable for a short-lived experiment, but shared environments need a more controlled path. A Terraform CI/CD workflow should make every change visible, testable, attributable, and recoverable.
The safest general pattern separates planning from applying:
- A pull request changes Terraform configuration or supporting files.
- CI formats and validates the configuration, initializes Terraform, and creates a plan.
- Reviewers inspect the plan alongside the code change.
- An approved workflow applies the exact reviewed plan in the intended environment.
- Scheduled checks look for drift or configuration problems that normal deployments may miss.
This approach is provider-agnostic. The same principles apply whether your infrastructure targets AWS, Azure, Google Cloud, or more than one provider. Provider credentials, backend configuration, approval controls, and runner security will vary, but the operational checklist remains similar.
Keep the first implementation small. A clear workflow for one environment is more valuable than a complicated framework that nobody can troubleshoot. As the team gains confidence, add reusable modules, policy checks, environment promotion, and drift detection.
Checklist by scenario
Before opening a pull request
- Choose a predictable repository layout. Keep root modules, reusable modules, environment variables, documentation, and workflow files easy to locate. For example, separate
modules/from environment directories such asenvs/dev/andenvs/prod/. - Pin important inputs deliberately. Declare the required Terraform version and provider constraints. Review upgrades as code changes rather than allowing an unplanned version change during a deployment.
- Format the configuration. Run
terraform fmt -check -recursivelocally or in CI so formatting problems do not reach review. - Validate each root module. Run
terraform initwith the appropriate backend settings, followed byterraform validate. Validation confirms that the configuration is structurally valid; it does not prove that the change is safe. - Review the blast radius. Check resource replacements, deletions, security-group or firewall changes, identity permissions, networking updates, and changes to persistent data.
When building the CI workflow
- Use separate jobs for quality checks and deployment. A formatting or validation failure should stop the workflow before credentials capable of changing production are made available.
- Run the same Terraform version everywhere. Local development, pull-request checks, and deployment runners should use a declared version or a controlled version-management approach.
- Create plans for the correct workspace or backend. A plan is meaningful only when it uses the intended state, variables, provider configuration, and account or subscription.
- Publish the plan safely. Make the plan available to reviewers without exposing sensitive values. Treat plan files and logs as potentially sensitive artifacts.
- Use least-privilege credentials. Pull-request jobs should not automatically receive unrestricted production write access. Prefer short-lived, federated credentials where the platform supports them, and restrict permissions by environment.
When promoting a change to an environment
- Require an explicit approval for sensitive environments. Use protected branches, environment approvals, or an equivalent control. Approval should be tied to the plan that will actually be applied.
- Apply a saved plan when practical. Generating a plan and applying a newly generated plan in separate steps can produce different results if infrastructure or variables change between them. A saved plan creates a clearer relationship between review and execution.
- Serialize state-changing operations. Prevent concurrent applies against the same state. Backend locking helps, but workflow concurrency controls can also stop two pipeline runs from competing.
- Record deployment context. Keep the commit identifier, actor, environment, Terraform version, and workflow result with the deployment record.
- Confirm the result. Review the apply output and run suitable smoke checks for critical resources. A successful Terraform command does not guarantee that an application is healthy.
When checking for drift
- Schedule a read-only plan. A recurring plan can reveal manual changes, provider-side updates, expired assumptions, or resources that no longer match the repository.
- Separate drift alerts from automatic remediation. Detecting drift is usually safer than immediately applying a corrective plan. Investigate the cause before deciding whether the code or the live environment should change.
- Define an owner for each alert. A drift check without an assigned response path quickly becomes background noise.
- Review exceptions explicitly. If a resource is intentionally managed outside Terraform, document that decision and use appropriate lifecycle or ownership controls rather than relying on team memory.
What to double-check
Remote state: Use a shared backend appropriate for the platform and team. Confirm that state locking, access control, recovery, and version history are understood. Never commit state files to the repository. State can contain resource metadata and values that should be treated as confidential.
Secrets: Do not place cloud credentials, API tokens, private keys, or sensitive variable values in Terraform files, committed variable files, pull-request comments, or unrestricted logs. Review how secrets enter the runner, whether they are masked, and whether forked pull requests or untrusted code can access them. For related operational guidance, see How to Secure GitHub Actions Secrets and Runners.
Plan interpretation: Look beyond the number of resources changing. A small plan can still be high risk if it changes a role policy, route, network boundary, backup setting, or data store. Replacements deserve particular attention because they may cause interruption or data loss depending on the resource.
Module boundaries: A module should represent a coherent capability with documented inputs and outputs. Avoid turning every small block into a module; excessive abstraction makes plans and debugging harder. Review third-party modules for version constraints, ownership, permissions, and maintenance before adopting them. The Terraform modules guide can support that evaluation process.
Provider and identity scope: Check that aliases, regions, subscriptions, projects, and accounts resolve to the intended targets. A syntactically valid configuration can still deploy to the wrong environment if a variable, role, or provider alias is misconfigured.
Common mistakes
- Applying directly from a laptop: This makes credentials, versions, state access, and deployment history dependent on one person. Keep emergency procedures documented, but make the normal path the automated path.
- Using one credential for every job: Pull-request validation, read-only drift detection, and production deployment have different permissions. Separate them where possible.
- Approving code without reading the plan: Code review cannot always reveal an unexpected replacement or provider interpretation. Require reviewers to inspect the plan summary and important resource-level changes.
- Replanning after approval without noticing: If variables, state, or the target account changes between approval and apply, the new plan may differ from the reviewed one. Make that difference visible or apply the saved plan.
- Ignoring failed applies: Terraform can make a partial change before an error. Do not simply rerun the job without checking state, live resources, and the error message.
- Making CI too permissive: Broad write permissions, untrusted pull-request execution, and unprotected production environments create avoidable paths to destructive changes. Harden the runner and workflow before adding more automation.
- Making CI too complicated: Multiple wrappers, hidden scripts, and environment-specific exceptions make failures difficult to diagnose. Prefer explicit commands, clear logs, and documented assumptions.
When to revisit
Review the workflow before seasonal planning cycles, major application releases, or a change in team ownership. Revisit it whenever Terraform, providers, CI runners, cloud accounts, identity roles, backends, or repository permissions change. These inputs can alter what a plan means even when the workflow file itself remains unchanged.
At minimum, schedule a recurring maintenance review that answers these questions:
- Are Terraform and provider versions still intentionally controlled?
- Can a pull request access production secrets or write permissions?
- Are state access, locking, recovery, and retention still appropriate?
- Do plan reviews and approvals apply to the exact artifact being deployed?
- Are drift alerts reviewed, actionable, and assigned?
- Does the workflow still match the repository structure and environment ownership?
- Have recent incidents or failed applies produced a documented improvement?
Start with one non-production environment: add formatting and validation, move state to a shared backend, generate plans in pull requests, and protect the apply step. Then test failure scenarios deliberately, including a rejected plan, a locked state, an expired credential, a provider error, and a partial apply. Once those paths are understandable, promote the same controls to higher-risk environments. That sequence gives your infrastructure automation a dependable foundation without requiring a large platform redesign.