One of the biggest mistakes beginners make when learning Terraform is treating their local machine as the deployment server.
A typical workflow looks like this:
terraform init
terraform plan
terraform apply
While this approach is perfectly fine for learning, it quickly becomes problematic when working on real-world projects with multiple engineers.
Consider these questions:
- Who deployed the infrastructure?
- Was the infrastructure reviewed before deployment?
- Can someone else reproduce the deployment?
- What happens if the engineer's laptop is lost or misconfigured?
- How do we know exactly what changed?
These are some of the reasons Infrastructure as Code (IaC) is almost always integrated with Continuous Integration and Continuous Deployment (CI/CD) pipelines in professional environments.
In this article, we'll build a simple Terraform CI/CD pipeline using GitHub Actions. Instead of focusing only on the YAML syntax, we'll first understand why each stage exists and how they work together to produce safe, repeatable infrastructure deployments.
What is Terraform CI/CD?
Terraform CI/CD is the process of automating the validation, planning, and deployment of infrastructure whenever changes are made to Terraform code.
Instead of running Terraform commands manually from a developer's laptop, a CI/CD platform executes those commands automatically in a controlled environment.
The workflow typically looks like this:
Developer
│
▼
Git Push
│
▼
GitHub Repository
│
▼
GitHub Actions
│
▼
Terraform Init
│
▼
Terraform Validate
│
▼
Terraform Plan
│
▼
Manual Approval
│
▼
Terraform Apply
│
▼
AWS Infrastructure
This approach provides consistency, visibility, and security while reducing the chances of human error.
Why Not Run Terraform Manually?
Running Terraform from your laptop works well for personal projects, but it introduces several risks in a team environment.
| Manual Deployment | CI/CD Deployment |
|---|---|
| Requires someone to remember every command | Runs automatically |
| Easy to skip validation | Validation is enforced |
| Difficult to track deployments | Every deployment is logged |
| Credentials live on developer laptops | Secrets are stored securely |
| Different Terraform versions on different machines | Consistent execution environment |
Automation allows every infrastructure change to follow the same process, regardless of who made the change.
What We'll Build
Our goal is to create two independent GitHub Actions workflows:
Workflow 1 – Continuous Integration (CI)
This workflow checks the quality of our Terraform code by running:
terraform fmtterraform initterraform validateterraform plan
Notice that this workflow never creates infrastructure.
Its job is simply to tell us whether the proposed changes are safe and valid.
Workflow 2 – Deployment
The deployment workflow is responsible for creating or updating infrastructure.
It runs only after we've decided that the Terraform plan is acceptable.
This separation is an important best practice because reviewing infrastructure changes is just as important as reviewing application code.
Project Structure
Our repository will look like this:
terraform-github-actions/
│
├── .github/
│ └── workflows/
│ ├── terraform-ci.yml
│ └── terraform-deploy.yml
│
├── main.tf
├── variables.tf
├── outputs.tf
Keeping the CI and deployment workflows separate makes them easier to understand, maintain, and extend as your project grows.
Understanding the CI Pipeline
The CI workflow is responsible for answering one question:
"Is this Terraform code ready to be deployed?"
To answer that question, it performs several checks.
Step 1: Check Formatting
Terraform enforces a consistent coding style.
Running:
terraform fmt -check
helps ensure every contributor follows the same formatting standards.
Step 2: Initialize Terraform
Before Terraform can validate or plan infrastructure, it must download the required providers and initialize the working directory.
This is done using:
terraform init
Step 3: Validate the Configuration
Next, Terraform verifies that the configuration is syntactically correct.
terraform validate
This catches missing arguments, invalid references, and other configuration issues before deployment.
Step 4: Generate an Execution Plan
Finally, Terraform generates an execution plan.
terraform plan
The plan shows exactly what Terraform intends to do without making any changes.
For example:
- Resources to be created
- Resources to be updated
- Resources to be destroyed
Reviewing this output is one of the most important parts of an Infrastructure as Code workflow.
Separating CI From Deployment
One question I often get from students is:
"If the CI workflow already runs
terraform plan, why do we need another deployment workflow?"
The answer is simple.
The CI workflow is designed to verify infrastructure.
The deployment workflow is designed to change infrastructure.
Keeping these responsibilities separate gives teams greater control over when deployments occur.
A typical process looks like this:
Push Code
│
▼
CI Pipeline
│
▼
Plan Generated
│
▼
Engineer Reviews Changes
│
▼
Deployment Triggered
│
▼
Terraform Apply
This approach prevents accidental deployments and encourages proper change review.
Managing AWS Credentials Securely
One mistake beginners frequently make is hardcoding AWS credentials inside Terraform code or GitHub workflows.
Never do this.
Instead, GitHub provides encrypted repository secrets that can be accessed securely during workflow execution.
This keeps sensitive information out of your source code and version history.
In future articles, we'll go one step further by replacing long-lived AWS access keys with GitHub's OpenID Connect (OIDC) integration, eliminating the need to store AWS credentials altogether.
What's Next?
In this article, we've explored the concepts behind Terraform CI/CD and why separating validation from deployment leads to safer infrastructure automation.
In the next article, we'll build our first GitHub Actions workflow from scratch, understand every line of the YAML file, and automate Terraform validation and planning for an AWS project.
By the end of the series, we'll have a production-ready pipeline capable of deploying infrastructure securely using GitHub Actions and Terraform.
Final Thoughts
Learning Terraform isn't just about writing .tf files.
Professional Infrastructure as Code is built on repeatable processes, code reviews, secure credential management, and automated deployments.
GitHub Actions gives us the tools to implement those practices, and Terraform provides the foundation for describing infrastructure as code.
Together, they enable teams to build reliable, scalable, and auditable cloud infrastructure.
If you're just starting your DevOps journey, mastering this workflow is one of the best investments you can make.
You can find the complete examples used in this article in my GitHub repository:
Top comments (1)
Nice repository for explaining the workflow. One thing I’d consider adding as the series progresses is remote state with locking, plus uploading the generated plan as an artifact so the reviewed plan is exactly the one that gets applied. That closes an important gap between learning Terraform and running it safely in production.