top of page

From Auto-Deploy to Infrastructure as Code

Writer: Aastha Thakker
Aastha Thakker
2 minutes ago
7 min read

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 Application

That 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 infrastructure

Instead 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
           ↓
       Infrastructure

A 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
  ↓
Destroy

HashiCorp 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 init

This 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 validate

This 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 plan

You might see something conceptually like:

+ create
~ update
- destroy

For example:

Plan:
+ Create storage bucket
~ Update firewall rule
- Destroy old resource

Nothing 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 apply

Terraform executes the operations required to bring the actual infrastructure in line with the configuration.


Conceptually:

Terraform configuration
        +
Current infrastructure/state
        ↓
       Plan
        ↓
      Apply
        ↓
Updated infrastructure

In 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 destroy

It 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 removed

Obviously, 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.tfstate

The state helps Terraform map the configuration to the actual resources it manages.


A simplified view is:

Terraform configuration
          +
     Terraform state
          +
   Current infrastructure
          ↓
      Terraform
          ↓
     Proposed changes

This 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 terraform

We can now create our first Terraform file. On Windows Command Prompt, an empty file can be created with:

New-Item main.tf -ItemType File

You should now have:

terraform
└── main.tf

2. Add Terraform configuration

Open the file:

notepad main.tf

Now 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 init

Terraform 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.tf

The .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 fmt

Then check whether the configuration is valid:

terraform validate

A successful validation looks like:


5. Run Terraform Plan


Now comes one of the most important commands:

terraform plan

Terraform 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 apply

Terraform 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.*.log

Now 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 main

And that’s it. Terraform is officially on our machine, inside our repository, initialized, validated, and committed.


But before anyone gets too excited, we haven’t connected Terraform to GitHub Actions, configured any cloud infrastructure, or automated a single infrastructure deployment yet. And that’s intentional.


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


bottom of page