Skip to main content

Terraform Tutorial for .NET Developers: AWS & Azure IaC

Learn Terraform for .NET developers with this hands-on tutorial. Deploy ASP.NET Core apps to AWS and Azure using infrastructure as code. Start today!

If you're a .NET developer who has ever clicked through the Azure Portal or AWS Console to create resources by hand, you already know the pain: environments drift apart, nobody remembers who changed what, and reproducing production in a staging environment takes days. This Terraform tutorial is written specifically for .NET developers who want to solve that problem with infrastructure as code (IaC) — defining your cloud infrastructure in version-controlled files, just like your C# code.

In this guide, you'll learn what Terraform is, why it beats manual provisioning, and how to deploy a real ASP.NET Core application to both Azure App Service and AWS Elastic Beanstalk using Terraform. We'll also cover best practices, common pitfalls, and how Terraform compares to Bicep and AWS CloudFormation — the questions every .NET team asks before adopting it.

What Is Terraform and Why Should .NET Developers Care?

Terraform, created by HashiCorp, is the most widely used infrastructure as code tool in the industry. You describe your desired infrastructure — app services, databases, storage accounts, load balancers — in declarative configuration files written in HCL (HashiCorp Configuration Language). Terraform then compares your desired state to what actually exists in the cloud and creates, updates, or destroys resources to make them match.

Here's why this matters for .NET teams specifically:

  • One tool for AWS and Azure. Many enterprise .NET shops are multi-cloud. Azure Bicep only works on Azure; CloudFormation only works on AWS. Terraform works on both (plus GCP, Cloudflare, GitHub, and 3,000+ other providers), so your team learns one workflow.
  • Code review for infrastructure. Your infrastructure lives in Git next to your C# solution. Changes go through pull requests. terraform plan shows a diff of exactly what will change before anything is touched — like a compiler warning for your cloud.
  • Reproducible environments. Spin up an identical dev, staging, and production environment from the same code with different variable files. No more "it works in staging" mysteries caused by hand-configured settings.
  • It's declarative, like LINQ. You declare what you want, not how to get there. Terraform figures out the dependency graph and execution order, the same way LINQ figures out query execution.

Installing Terraform and the Core Workflow

Install Terraform on Windows with winget (or use Homebrew on macOS, apt on Linux):

winget install HashiCorp.Terraform
terraform -version

Every Terraform project follows the same four-command workflow. Memorize this — it's 90% of your daily usage:

  • terraform init — downloads providers (like restoring NuGet packages)
  • terraform plan — shows what will change (like a dry run)
  • terraform apply — makes the changes
  • terraform destroy — tears everything down

Terraform Tutorial: Deploy ASP.NET Core to Azure App Service

Let's deploy a real ASP.NET Core 8 app to Azure. Create a folder called infra next to your solution and add a file named main.tf:

# main.tf — Azure App Service for an ASP.NET Core app
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }
}

provider "azurerm" {
  features {}
}

variable "environment" {
  type    = string
  default = "dev"
}

resource "azurerm_resource_group" "main" {
  name     = "rg-csharpcoder-${var.environment}"
  location = "eastus2"
}

resource "azurerm_service_plan" "main" {
  name                = "asp-csharpcoder-${var.environment}"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  os_type             = "Linux"
  sku_name            = var.environment == "prod" ? "P1v3" : "B1"
}

resource "azurerm_linux_web_app" "main" {
  name                = "app-csharpcoder-${var.environment}"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  service_plan_id     = azurerm_service_plan.main.id

  site_config {
    application_stack {
      dotnet_version = "8.0"
    }
    always_on = var.environment == "prod"
  }

  app_settings = {
    "ASPNETCORE_ENVIRONMENT" = var.environment == "prod" ? "Production" : "Development"
  }
}

output "app_url" {
  value = "https://${azurerm_linux_web_app.main.default_hostname}"
}

Notice the ternary expressions — they work almost exactly like C#'s conditional operator. Production gets a Premium plan with always_on; dev gets a cheap Basic plan. Now deploy it:

az login                        # authenticate with Azure CLI
terraform init                  # download the azurerm provider
terraform plan                  # review: "Plan: 3 to add, 0 to change, 0 to destroy"
terraform apply -auto-approve   # create the resources

In under two minutes you have a resource group, an App Service plan, and a web app — and you can recreate them in any subscription, any region, any time. Publish your app with the .NET CLI:

dotnet publish -c Release -o ./publish
cd publish && zip -r ../app.zip .
az webapp deploy --resource-group rg-csharpcoder-dev `
  --name app-csharpcoder-dev --src-path ../app.zip --type zip

Terraform on AWS: The Same Skills, Different Provider

Here's the payoff of learning Terraform instead of a cloud-specific tool: deploying the same app to AWS uses identical concepts. Only the resource types change. This example provisions Elastic Beanstalk, AWS's closest equivalent to Azure App Service for .NET workloads:

# aws.tf — Elastic Beanstalk for the same ASP.NET Core app
provider "aws" {
  region = "us-east-1"
}

resource "aws_elastic_beanstalk_application" "main" {
  name        = "csharpcoder-app"
  description = "ASP.NET Core 8 application"
}

resource "aws_elastic_beanstalk_environment" "main" {
  name                = "csharpcoder-${var.environment}"
  application         = aws_elastic_beanstalk_application.main.name
  solution_stack_name = "64bit Amazon Linux 2023 v3.5.6 running .NET 8"

  setting {
    namespace = "aws:autoscaling:launchconfiguration"
    name      = "InstanceType"
    value     = var.environment == "prod" ? "t3.medium" : "t3.micro"
  }

  setting {
    namespace = "aws:elasticbeanstalk:application:environment"
    name      = "ASPNETCORE_ENVIRONMENT"
    value     = var.environment == "prod" ? "Production" : "Development"
  }
}

The mental model — providers, resources, variables, outputs, plan, apply — is exactly the same. That transferability is why Terraform consistently tops developer surveys for infrastructure tooling.

Terraform State: The Concept That Trips Up Every Beginner

Terraform records everything it manages in a state file (terraform.tfstate). This file maps your HCL code to real cloud resource IDs. Two rules will save you enormous pain:

  • Never commit state to Git. It contains secrets in plain text (connection strings, keys). Add *.tfstate* and .terraform/ to .gitignore immediately.
  • Use remote state for teams. Local state breaks the moment two developers run apply. Store state in Azure Blob Storage or AWS S3 with locking:
terraform {
  backend "azurerm" {
    resource_group_name  = "rg-terraform-state"
    storage_account_name = "stcsharpcodertfstate"
    container_name       = "tfstate"
    key                  = "csharpcoder.prod.tfstate"
    use_azuread_auth     = true
  }
}

Think of remote state like a shared database with row locking, versus local state being an in-memory dictionary — fine solo, catastrophic under concurrency.

Best Practices for .NET Teams Using Terraform

1. Structure your repo like a .NET solution

Keep infrastructure in an infra/ folder in the same repository as your application, with one folder (or variable file) per environment. Use modules the way you use class libraries — reusable, parameterized building blocks:

infra/
├── modules/
│   └── webapp/          # reusable module, like a class library
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── environments/
│   ├── dev.tfvars
│   └── prod.tfvars
└── main.tf

2. Never hardcode secrets

Reference Azure Key Vault or AWS Secrets Manager instead of putting connection strings in .tf files. In App Service, use Key Vault references in app_settings so secrets never touch your repo or your state outputs.

3. Run Terraform in CI/CD, not on laptops

Production changes should flow through GitHub Actions or Azure DevOps: plan on pull request (posted as a PR comment for review), apply on merge. This gives you an audit trail and eliminates "who has the right credentials on their machine" problems.

4. Pin your provider versions

Just as you pin NuGet package versions, pin providers with ~> 4.0 constraints and commit the .terraform.lock.hcl file. Unpinned providers are how a routine Tuesday deploy becomes an outage.

Common Pitfalls to Avoid

  • Editing resources in the portal after Terraform created them. This causes drift — the next apply may silently revert someone's "quick fix." Make the rule absolute: if Terraform manages it, only Terraform changes it. Use terraform plan regularly to detect drift.
  • Ignoring the plan output. The single most important habit is reading the plan before applying. A resource marked -/+ destroy and then create replacement means downtime and possible data loss — for example, changing a database server name forces recreation.
  • Running terraform destroy against the wrong workspace. Separate state files per environment and require manual approval gates for production applies in your pipeline.
  • Treating Terraform as a deployment tool. Terraform provisions infrastructure; it shouldn't push your application binaries. Deploy your ASP.NET Core code with dotnet publish plus your pipeline's deploy step, and let Terraform own the platform underneath.

Terraform vs Bicep vs CloudFormation: Which Should You Choose?

If your organization is 100% Azure forever, Bicep is a fine choice with excellent Azure integration and no state file to manage. If you're 100% AWS, CloudFormation (or the CDK, which lets you write infrastructure in C#) is viable. But if there's any chance you'll touch a second cloud, manage GitHub repos, DNS, or SaaS tooling as code, Terraform's single workflow across thousands of providers wins. Its job-market value is also significantly higher — "Terraform" appears in far more DevOps and platform engineering job postings than Bicep and CloudFormation combined.

Conclusion: Key Takeaways from This Terraform Tutorial

This Terraform tutorial covered everything a .NET developer needs to start practicing infrastructure as code today. The key takeaways:

  • Terraform lets you define AWS and Azure infrastructure declaratively, version it in Git, and review changes like any C# pull request.
  • The core workflow is four commands: init, plan, apply, destroy — and plan is the one you should never skip reading.
  • State is the heart of Terraform: keep it out of Git, store it remotely with locking, and separate it per environment.
  • Structure infrastructure like a .NET solution — modules as class libraries, tfvars per environment — and run everything through CI/CD.
  • Choose Terraform over Bicep or CloudFormation when portability, ecosystem breadth, or career value matters.

Your next step: take the Azure example above, run terraform apply against a free-tier subscription, then terraform destroy when you're done — the full loop costs pennies and teaches more than any article can. Once that clicks, move your real project's infrastructure into code, one resource at a time.

About csharp-coder.com
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.

This article is part of the cloud and security guide (Azure, AWS, Google Cloud, infrastructure as code and security).

Comments

Popular posts from this blog

.NET MAUI Tutorial 2026: Build Cross-Platform Apps in C#

Learn .NET MAUI in 2026 to build iOS, Android, Windows & Mac apps from one C# codebase. Start this cross-platform tutorial with code examples today. .NET MAUI (Multi-platform App UI) is Microsoft's framework for building native iOS, Android, Windows, and macOS apps from a single C# codebase . If you've ever wanted to ship a mobile app without learning Swift, Kotlin, and Win32 separately, this .NET MAUI tutorial for 2026 is your starting point. In this guide you'll learn what .NET MAUI is, why it matters for cross-platform app development in C#, and how to build your first working app — with runnable code examples and the best practices senior engineers actually use in production. What Is .NET MAUI and Why Use It in 2026? .NET MAUI is the evolution of Xamarin.Forms, fully integrated into the modern .NET runtime. With one project and one language — C# — you target four platforms. The framework compiles to native UI controls on each device, so a button on iOS...

Angular 14 : 404 error during refresh page after deployment

In this article, We will learn how to solve 404 file or directory not found angular error in production.  Refresh browser angular 404 file or directory not found error You have built an Angular app and created a production build with ng build --prod You deploy it to a production server. Everything works fine until you refresh the page. The app throws The requested URL was not found on this server message (Status code 404 not found). It appears that angular routing not working on the production server when you refresh the page. The error appears on the following scenarios When you type the URL directly in the address bar. When you refresh the page The error appears on all the pages except the root page.   Reason for the requested URL was not found on this server error In a Multi-page web application, every time the application needs to display a page it has to send a request to the web server. You can do that by either typing the URL in the address bar, clicking on the Me...

Angular 14 CRUD Operation with Web API .Net 6.0

How to Perform CRUD Operation Using Angular 14 In this article, we will learn the angular crud (create, read, update, delete) tutorial with ASP.NET Core 6 web API. We will use the SQL Server database and responsive user interface for our Web app, we will use the Bootstrap 5. Let's start step by step. Step 1 - Create Database and Web API First we need to create Employee database in SQL Server and web API to communicate with database. so you can use my previous article CRUD operations in web API using net 6.0 to create web API step by step. As you can see, after creating all the required API and database, our API creation part is completed. Now we have to do the angular part like installing angular CLI, creating angular 14 project, command for building and running angular application...etc. Step 2 - Install Angular CLI Now we have to install angular CLI into our system. If you have already installed angular CLI into your system then skip this step.  To install angular CLI ope...