Add required tags, input validation and a budget alert so infrastructure stays labelled, within policy and within budget.
Why it matters
The cloud makes it easy to create resources and just as easy to forget them. Real teams find untagged servers nobody owns, test databases left running for months, and a GPU instance someone started "for an hour". A large part of a cloud engineer's value is preventing that: every resource should have an owner, stay within agreed rules and show up in a budget that someone watches. This practice of managing cloud spending as a shared responsibility of engineering and finance is called FinOps.
Infrastructure as code is the best place for these guardrails, because rules written in code apply to every change automatically.
Guardrail 1: tags on everything
Tags (called labels on Google Cloud and Docker) are key-value pairs attached to resources. Billing tools group costs by tag, so tags answer "which team, project and environment is this money for?" A typical required set:
| Tag | Example | Why |
|---|---|---|
owner | team-payments | Who to ask before deleting it |
project | hello-devops | Cost reports per product |
env | staging | Separate production cost from test cost |
managed-by | terraform | Warns people not to edit it by hand |
In Terraform, define tags once and apply them everywhere. The AWS provider has default_tags, which adds them to every resource it creates:
provider "aws" {
region = "us-east-1"
default_tags {
tags = {
owner = "team-payments"
project = "hello-devops"
env = var.env
managed-by = "terraform"
}
}
}
With the local Docker provider you can practise the same idea using container labels and a dynamic block:
locals {
tags = merge(var.tags, { env = var.env, managed-by = "terraform" })
}
resource "docker_container" "web" {
name = "tf-web-${var.env}"
image = docker_image.nginx.image_id
dynamic "labels" {
for_each = local.tags
content {
label = labels.key
value = labels.value
}
}
}
Guardrail 2: validation and policy
Terraform variable validation rejects bad input during plan, before anything is created:
variable "env" {
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.env)
error_message = "env must be dev, staging or prod."
}
}
variable "tags" {
type = map(string)
validation {
condition = alltrue([for k in ["owner", "project"] : contains(keys(var.tags), k)])
error_message = "tags must include owner and project."
}
}
variable "instance_size" {
type = string
default = "small"
validation {
condition = contains(["small", "medium"], var.instance_size)
error_message = "Only small or medium sizes are allowed without an approved exception."
}
}
The same idea scales up through policy as code: tools such as Open Policy Agent (with Conftest), Checkov, tfsec/Trivy, or HashiCorp Sentinel check a plan in CI against rules like "no public storage buckets", "every resource has an owner tag" or "no instance types larger than X". Cloud providers add their own account-level controls (AWS Service Control Policies, Azure Policy, Google Cloud Organization Policy). A rule in CI catches mistakes during review; an account-level rule catches what slips past CI.
Guardrail 3: budgets and alerts
A budget alert emails you when actual or forecast spending crosses a threshold. Every major cloud has one: AWS Budgets, Azure Cost Management budgets and Google Cloud Billing budgets. Create one in every account you use, including practice accounts, before you create anything else. A sensible practice setup is a small monthly budget (for example 5 or 10 in your currency) with alerts at 50%, 80% and 100% of actual spend and 100% of forecast.
Two honest warnings:
- A budget alert does not stop spending. It tells you; you still have to act. Some providers let you attach automated actions, but they need careful setup.
- Billing data can lag by hours. Do not wait for an alert to clean up: delete practice resources as soon as you finish.
Finding and cutting waste
The usual big wins are boring: delete unused resources (idle VMs, unattached disks, old snapshots, unused public IP addresses and load balancers), right-size instances that run at 5% CPU, stop non-production environments at night, set lifecycle rules on storage, and use reserved or committed-use pricing only for steady workloads. Tags make all of this possible, because you can see what each resource is for.
Common mistakes
- Tagging by hand, so tags are missing or spelled five ways (
Env,env,environment). - Treating a budget alert as a spending cap.
- Validation messages that do not say how to fix the problem.
- Policies so strict that people work around them; start with warnings, then enforce.
Quick recap
- Required tags (owner, project, env) make cost and ownership visible.
- Variable validation and policy-as-code reject bad infrastructure at plan time.
- Set a budget alert in every account first; it warns but does not stop spending.
- Delete unused resources promptly; tags tell you which ones they are.
Try it
The Do tab has you add tag and validation guardrails to your local Terraform project and set a budget alert on your cloud practice account.