Icons Technology Icons Technology.

Remote-first · Offices in EU & US

hello@icontechnology.example

cloud-devops Sep 16, 2026

Practical Guide to Infrastructure as Code with Terraform and GitHub Actions

By Elena Volkov
Practical Guide to Infrastructure as Code with Terraform and GitHub Actions

Why Infrastructure as Code matters

Manual infrastructure configuration is error-prone, undocumented, and impossible to reproduce consistently. Infrastructure as Code (IaC) treats infrastructure definitions as software: version-controlled, testable, and reviewable.

At Icon Technology, we use Terraform for provisioning and GitHub Actions for CI/CD. This combination gives us a fully automated pipeline from code change to infrastructure deployment.

Project structure

A clean Terraform project separates environment configuration from reusable modules:

infrastructure/
├── modules/
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── ecs/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── rds/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── environments/
│   ├── staging/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   └── production/
│       ├── main.tf
│       ├── variables.tf
│       └── terraform.tfvars
└── .github/
    └── workflows/
        └── infrastructure.yml

The CI/CD pipeline

Every infrastructure change goes through the same pipeline: plan, review, apply. The GitHub Actions workflow runs terraform plan on every pull request, posts the plan output as a comment, and requires approval before applying changes to production.

This approach caught several issues before they reached production: an S3 bucket with public access enabled, a security group rule that was too permissive, and a database instance type change that would have doubled our monthly cost.

State management

Terraform state must be stored remotely and locked to prevent concurrent modifications. We use S3 with DynamoDB locking. The state file contains sensitive information (database passwords, API keys), so encryption at rest is essential.

We also enabled state file versioning. On two occasions, this allowed us to roll back to a known-good state after a misconfigured apply.

Testing Terraform

Code review is not enough for infrastructure. We use Terratest to write automated tests that provision real infrastructure, verify it works, and tear it down. These tests run in a dedicated AWS account and catch issues that static analysis misses.

func TestVpcModule(t *testing.T) {
    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../modules/vpc",
        Vars: map[string]interface{}{
            "environment": "test",
            "vpc_cidr":    "10.0.0.0/16",
        },
    })
    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)
    vpcId := terraform.Output(t, terraformOptions, "vpc_id")
    assert.Regexp(t, "^vpc-", vpcId)
}
Start Your Next Initiative

Aligned with your growth. Simple, fair, transparent.

Tell us what you want to build. We will come back within one business day with a clear plan and architectural roadmap.