From Auto-Deploy to Infrastructure as Code

In my previous blog, we took a project from a GitHub repository to a live website and then automated its deployment using GitHub Actions.
The pipeline looked roughly like this:
Code
↓
GitHub
↓
GitHub Actions
↓
Build → Test → Deploy
↓
Live ApplicationThat solved one part of the problem: automating application delivery.
But there is another layer sitting underneath the application.
Where does the infrastructure come from?
A real application may depend on virtual machines, networks, databases, storage, IAM permissions, load balancers and several other cloud resources. If those resources are also going to be managed through code and version control, we need to go one step further.
This is where Infrastructure as Code (IaC) and Terraform come in.
What is Infrastructure as Code?
Infrastructure as Code means describing infrastructure using configuration files instead of relying on a sequence of clicks in a cloud console. The idea is simple:
Infrastructure requirements
↓
Code files
↓
Terraform
↓
Cloud infrastructureInstead of saying:
Create a server, configure this network, attach this storage and give it these permissions.
we describe the desired infrastructure state in code.
That code can then live in GitHub, go through pull requests, be reviewed, versioned and eventually executed through a CI/CD pipeline.
This makes infrastructure easier to manage, just like application code. We can save it in GitHub, track changes, review updates and use the same setup again whenever needed.
Terraform is an Infrastructure as Code tool created by HashiCorp. Instead of telling Terraform every step it needs to follow, we describe what we want our infrastructure to look like. Terraform then checks the current setup and makes the required changes to match it.
Why Terraform?
Terraform is useful because it is not tied to only one type of infrastructure.
Through providers, Terraform can interact with platforms such as AWS, Azure, Google Cloud, GitHub, Cloudflare and many others.
The basic relationship looks like this:
Terraform
│
├── Provider
│ ↓
│ AWS / GCP / Azure / ...
│
└── Resources
↓
InfrastructureA provider acts as the connection between Terraform and the platform being managed.
A resource represents something Terraform manages, such as a virtual machine, storage bucket, network or database.
A Module allows you to encapsulate and reuse Terraform configurations. They are particularly useful for organizing and abstracting complex infrastructure code.
Outputs are used to expose values from your Terraform configuration. This is helpful for retrieving information about the infrastructure after it has been created.

The Terraform workflow
If you remember only one section from this blog, make it this one. The basic Terraform workflow can be understood as:
Write
↓
Initialize
↓
Validate
↓
Plan
↓
Apply
↓
DestroyHashiCorp describes the broader Terraform workflow around three major stages: Write, Plan and Apply. The commands below add the practical steps you commonly use while working through that workflow.
1. terraform init
Before Terraform can work with your configuration, the working directory needs to be initialized.
terraform initThis prepares the Terraform environment and downloads the providers and modules required by the configuration.
It also prepares the backend used for storing Terraform state.
2. terraform validate
Next, we can check whether the Terraform configuration itself is valid.
terraform validateThis catches configuration problems before we move further into the workflow.
It is important to understand what this does not mean.
A configuration passing validation does not mean that Terraform has checked whether the infrastructure is suitable, secure or even affordable.
It primarily tells us that the configuration is structurally valid enough for Terraform to process. That distinction becomes important once validation is added to CI/CD.
3. terraform plan
Terraform compares the desired configuration with its knowledge of the current infrastructure and generates a proposed set of changes.
terraform planYou might see something conceptually like:
+ create
~ update
- destroyFor example:
Plan:
+ Create storage bucket
~ Update firewall rule
- Destroy old resourceNothing is changed just because you ran terraform plan.
That is what makes it so useful for pull requests and infrastructure reviews. You can inspect the proposed changes before allowing them to happen.
4. terraform apply
Once the proposed changes are reviewed, Terraform can apply them.
terraform applyTerraform executes the operations required to bring the actual infrastructure in line with the configuration.
Conceptually:
Terraform configuration
+
Current infrastructure/state
↓
Plan
↓
Apply
↓
Updated infrastructureIn an interactive workflow, Terraform normally asks for confirmation before making the changes.
In an automated pipeline, however, we don’t want a GitHub Actions runner sitting there waiting for someone to type yes. That is where automated approval controls and commands such as -auto-approve become relevant.
But automatically applying infrastructure changes should be treated carefully. Removing the human approval step means the pipeline needs stronger controls around what is allowed to reach apply.
5. terraform destroy
There is the opposite operation:
terraform destroyIt generates a plan for removing resources managed by the Terraform configuration and, after confirmation, deletes them. This is particularly useful while experimenting with temporary infrastructure.
For example:
terraform apply
↓
Infrastructure created
↓
Experiment completed
↓
terraform destroy
↓
Resources removedObviously, this is a command that deserves considerably more caution in a production environment.
Terraform State
There is one Terraform concept that we often encounter only after something goes wrong: state.
Terraform needs a record of the infrastructure it is managing.
That information is maintained in Terraform state.
With the default local setup, this can appear as:
terraform.tfstateThe state helps Terraform map the configuration to the actual resources it manages.
A simplified view is:
Terraform configuration
+
Terraform state
+
Current infrastructure
↓
Terraform
↓
Proposed changesThis is also why Terraform state should not be treated like an ordinary source-code file.
In a team environment, keeping state only on one developer’s machine creates obvious problems. Teams commonly use a remote backend so that the state can be shared and managed appropriately, often with locking to prevent conflicting operations. Terraform’s initialization process is also responsible for configuring the backend when one is specified.
And this gives us an important rule:
Do not casually commit sensitive Terraform state into a public repository.
State can contain information about the infrastructure Terraform manages, and depending on the configuration, sensitive values can also appear in it.
Adding Terraform to the Existing GitHub Actions Project
Now let’s take the Terraform concept and connect it to the same GitHub repository and GitHub Actions workflow from the previous blog. Your project already contains the application files and the GitHub Actions workflow from the previous setup.
The important part is that we are adding Terraform to this existing project, not creating a completely separate repository.
1. Create a Terraform directory
From the root of the repository, create a directory for Terraform:
mkdir terraform
# Move into it:
cd terraformWe can now create our first Terraform file. On Windows Command Prompt, an empty file can be created with:
New-Item main.tf -ItemType FileYou should now have:
terraform
└── main.tf2. Add Terraform configuration
Open the file:
notepad main.tfNow we can add our Terraform configuration.
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
variable "project_id" {
type = string
}
variable "region" {
type = string
default = "us-central1"
}At this point, we have not created any cloud infrastructure. We have only described the provider and the values Terraform will use.
3. Initialize Terraform
Make sure you are inside the Terraform directory:
cd terraform
terraform initTerraform will initialize the working directory and download the required provider.
If everything is successful, you should see a message similar to:

A new directory will also appear:
terraform/
│
├── .terraform/
└── main.tfThe .terraform directory contains files Terraform uses internally. It should not normally be committed to Git.
4. Format and validate the configuration
Terraform can automatically format Terraform files:
terraform fmtThen check whether the configuration is valid:
terraform validateA successful validation looks like:

5. Run Terraform Plan
Now comes one of the most important commands:
terraform planTerraform evaluates the configuration and determines what changes would be required. At this stage, Terraform is planning the changes, not applying them.
This is useful because we can inspect the proposed infrastructure changes before anything is created or modified.
6. Apply the configuration
Once the plan looks correct, Terraform can apply it:
terraform applyTerraform will show the proposed changes and ask for confirmation. Type “yes”. Terraform will then make the required changes.

This is the basic Terraform workflow we will later automate using GitHub Actions.
7. Edit .gitignore
We don’t want Terraform’s local working directory or state files to accidentally become part of our Git repository.
.terraform/
*.tfstate
*.tfstate.*
crash.log
crash.*.logNow check, “git status”. Terraform’s .terraform directory should no longer appear as something to commit.
8. Commit the changes
git add .
git commit -m "Adding terraform"
git push origin mainAnd that’s it. Terraform is officially on our machine, inside our repository, initialized, validated, and committed.
The goal of this blog was simply to get comfortable with Terraform and understand what happens before the real automation begins. One step at a time because trying to learn Terraform, GitHub Actions, cloud authentication, state management, and infrastructure deployment in one blog sounds like a great way to make both of us close the tab.
In the next blog, we’ll take this setup further and actually connect Terraform + GitHub Actions + Cloud, turning what we have here into a proper infrastructure workflow.
So, if you found this useful, give it a clap, share it with someone who is also trying to make sense of Terraform, and subscribe if you want to catch the next part.
And if you’re already waiting for the GitHub Actions + cloud part… well, you know what to do “STay TUned”.
Okay, enough of Terraform for today. Now go treat yourself to something handmade. Explore my resin creations on my website, find a piece you love, and surely place an order because your next favourite piece might just be waiting for you. HetAas Atelier: Handmade Resin Treasures



Comments