If you just arrived and want to understand what has been done so far, I recommend taking a step back and checking out the previous posts:

Terraform: Taking the Next Step – Building Infrastructure from Scratch on GCP

This post (and the next one) will probably be a bit more theoretical than I like, but let’s take an important step: organizing the project with best practices. We’ll better understand the use of variables, outputs – which we created previously. And we’ll take the opportunity to modularize the project a bit more.

What we’ll see today:

  • How to declare and use variables correctly
  • How to use outputs to facilitate integration and debugging
  • Variable types in Terraform
  • How to structure your project in a clean and scalable way
  • First best practices for working with Terraform professionally

1. Variable types

In Terraform, we can use various types of variables, such as:

  • string: text value
  • number: numeric value
  • bool: true or false
  • list: list of values, e.g.: ["dev", "staging", "prod"]
  • map: key-value structure, e.g.: { dev = "10.0.0.0/16", prod = "10.1.0.0/16" }
  • object: more complex structure

List example:

variable "regions" {<br>type = list(string)<br>default = ["us-central1", "us-east1"]<br>}

2. Outputs

Outputs are ways to export useful information after apply, such as IPs, resource names, or service account emails that were created in the process.

Let’s go to a practical example:

Example of outputs.tf in a module:

output "instance_ip" {
  value = google_compute_instance.vm.network_interface[0].access_config[0].nat_ip
}

How do we access this output in another module or environment:

If you use terraform_remote_state, you can do this, for example:

data "terraform_remote_state" "dev" {
  backend = "gcs"
  config = {
    bucket = "my-terraform-states"
    prefix = "dev/infrastructure"
  }
}

resource "google_dns_record_set" "record" {
  name = "app.dev.example.com."
  type = "A"
  ttl = 300
  rrdatas = [data.terraform_remote_state.dev.outputs.instance_ip]
}

This allows you to “chain” infrastructures: the output from the dev environment can feed prod, or one module can feed another.

This is useful for integrating with other modules, CI/CD tools or just to know what was created (as we did in the previous post)

3. Structure best practices

Minimal and clean structure:

Terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars

Some suggestions:

  • Use the variables.tf file only to declare variables, without values.
  • Keep sensitive values **outside the repository**, i.e., put the .tfvars in .gitignore.
  • Separate environments (dev, prod) with different files or using workspaces (we'll talk about this later)

4. Other important best practices

  • Always use terraform plan before apply
  • Version Terraform (.terraform-version or in required_version – more at [Managing Terraform Versions](https://developer.hashicorp.com/terraform/tutorials/configuration-language/versions))
  • Use remote backends for state (we'll talk about this in the next post!)
  • Comment and document your code
  • Start thinking about **modules** for reusable components (we'll talk about this soon too!)