Å klikke seg gjennom AWS-konsollet for å bygge en VPC, tre subnett, en EC2-instans og en sikkerhetsgruppe tar kanskje 20 minutter første gang. Andre gang tar det like lenge, og du husker garantert ikke alle innstillingene du valgte. Terraform løser dette ved å beskrive infrastrukturen din som kode, slik at hele oppsettet kan bygges, endres og slettes med tre kommandoer. I denne guiden går vi steg for steg gjennom å installere Terraform, koble til AWS, bygge en fungerende VPC med EC2-instans, håndtere state sikkert, og unngå fellene som oftest ødelegger produksjonsmiljøer.

Terraform er fortsatt det mest brukte verktøyet for infrastructure as code (IaC) i 2026, men landskapet har endret seg. IBM kjøpte HashiCorp for rundt 5,6 milliarder euro, og Terraform kjører nå under en Business Source License (BSL) i stedet for den gamle åpen kildekode-lisensen. Det har fått en del team til å vurdere forket OpenTofu, som ifølge Scalrs 2026-data har rundt 12 % adopsjon blant IaC-praktikere, mens Terraform fortsatt dominerer med mellom 33 og 62 % avhengig av undersøkelse. Vi kommer tilbake til hva lisensendringen faktisk betyr for deg som utvikler, men først: la oss bygge noe.

Hva er Terraform, og hvorfor bruke det til AWS?

Terraform er et open-source (nå source-available) verktøy fra HashiCorp som lar deg definere skyinfrastruktur i deklarative konfigurasjonsfiler skrevet i HashiCorp Configuration Language (HCL). I stedet for å klikke i AWS-konsollet, skriver du hva sluttresultatet skal være, og Terraform regner ut hvilke API-kall som trengs for å komme dit. Ifølge HashiCorps offisielle dokumentasjon er “Terraform et infrastructure as code-verktøy som lar deg bygge, endre og versjonere infrastruktur trygt og effektivt” (developer.hashicorp.com).

Fordelen fremfor manuell klikking eller AWS CloudFormation er tredelt. For det første er Terraform sky-agnostisk: samme verktøy og samme syntaks fungerer for AWS, Azure, GCP, Cloudflare og over 4000 andre leverandører i Terraform Registry. For det andre får du en plan-fase før noe endres, slik at du ser nøyaktig hva som skal opprettes, endres eller slettes før du trykker apply. For det tredje lagres hele infrastrukturtilstanden i en state-fil, som gjør det mulig å spore drift (avvik mellom kode og virkelighet) og rulle tilbake endringer.

For norske og nordiske team som kjører arbeidslast i AWS-regionen eu-north-1 (Stockholm), gir Terraform i tillegg en fordel når kravene om dokumentert infrastruktur skjerpes. Med NIS2 og strengere internkontrollkrav i finans- og energisektoren er det langt enklere å vise revisor en Git-historikk med IaC-endringer enn å rekonstruere hva som ble gjort manuelt i konsollet for åtte måneder siden.

Forutsetninger og verktøyversjoner

Før du starter trenger du følgende på plass. Sjekk versjonene nøye, siden Terraform og AWS-provideren oppdateres hyppig, og eldre versjoner kan mangle ressurser som brukes i denne guiden.

VerktøyAnbefalt versjon (august 2026)Formål
Terraform CLI1.15.9 eller nyereKjører plan/apply/destroy lokalt
terraform-provider-aws6.60.0 eller nyereOffisiell HashiCorp-plugin mot AWS API
AWS CLI2.x (siste stabile)Autentisering og feilsøking mot AWS
Git2.4x eller nyereVersjonskontroll av .tf-filer
OperativsystemLinux, macOS eller WSL2 på WindowsTerraform kjører på alle tre

Du trenger også en AWS-konto med et IAM-brukernavn eller en IAM-rolle som har tilstrekkelige rettigheter til å opprette VPC-er, EC2-instanser og sikkerhetsgrupper. Bruk aldri root-brukeren til dette. Sett opp en dedikert IAM-bruker med programmatisk tilgang, eller enda bedre: bruk AWS IAM Identity Center med midlertidige credentials. Til slutt trenger du en tekstredigerer (VS Code med HashiCorp Terraform-utvidelsen gir syntaksmarkering og validering underveis).

Steg 1: Installer Terraform CLI

På macOS med Homebrew installerer du Terraform slik at HashiCorps offisielle tap alltid gir siste stabile versjon:

brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform version

På Ubuntu eller Debian (og WSL2) bruker du HashiCorps APT-repo:

wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
terraform version

Kommandoen terraform version skal returnere noe i retning av Terraform v1.15.9. Hvis du får en versjon eldre enn 1.6, bør du oppgradere før du fortsetter, siden enkelte funksjoner i denne guiden (som moved-blokker og forbedret feilsøking) krever nyere versjoner.

Steg 2: Konfigurer AWS-legitimasjon riktig

Dette er stedet der de fleste nybegynnere gjør en sikkerhetsfeil: de limer AWS-nøkler rett inn i .tf-filene. Ikke gjør det. Bruk i stedet AWS CLI til å lagre credentials lokalt, eller enda bedre, miljøvariabler som aldri havner i Git.

# Alternativ A: AWS CLI-profil (anbefalt for lokal utvikling)
aws configure --profile terraform-tutorial
# Fyll inn Access Key ID, Secret Access Key, region: eu-north-1

# Alternativ B: Miljøvariabler (anbefalt for CI/CD)
export AWS_ACCESS_KEY_ID="din-access-key"
export AWS_SECRET_ACCESS_KEY="din-secret-key"
export AWS_DEFAULT_REGION="eu-north-1"

Test at legitimasjonen fungerer før du rører Terraform i det hele tatt:

aws sts get-caller-identity --profile terraform-tutorial

Du skal se en JSON-respons med UserId, Account og Arn. Får du en ExpiredToken– eller InvalidClientTokenId-feil, er nøklene enten utløpt eller feil limt inn. Sjekk at du ikke har mellomrom eller linjeskift i secret-nøkkelen.

Steg 3: Sett opp prosjektstrukturen

Lag en mappe for prosjektet og en enkel filstruktur som skiller variabler, hovedressurser og output fra hverandre. Dette er ikke strengt nødvendig for et lite prosjekt, men det er vanen du bør bygge fra dag én, siden det skalerer naturlig til større miljøer.

mkdir terraform-aws-tutorial && cd terraform-aws-tutorial
touch main.tf variables.tf outputs.tf terraform.tfvars .gitignore

# Legg til state-filer og hemmeligheter i .gitignore med en gang
cat >> .gitignore << 'EOF'
*.tfstate
*.tfstate.backup
.terraform/
terraform.tfvars
*.pem
EOF

Merk at terraform.tfvars havner i .gitignore hvis den inneholder hemmeligheter. Hvis filen kun inneholder ufarlige verdier som regionnavn, kan den committes, men vær bevisst på hva som faktisk står der før du bestemmer deg.

Steg 4: Deklarer provideren og lås versjoner

Åpne main.tf og legg inn en terraform-blokk som spesifiserer hvilken versjon av Terraform-selve og hvilken versjon av AWS-provideren prosjektet krever. Å låse versjoner er kritisk: uten det kan et terraform init på en kollegas maskin trekke inn en nyere providerversjon med endret oppførsel, og du får et miljø som ikke er reproduserbart.

terraform {
  required_version = ">= 1.15.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.60"
    }
  }
}

provider "aws" {
  region  = var.aws_region
  profile = "terraform-tutorial"

  default_tags {
    tags = {
      Project     = "terraform-aws-tutorial"
      ManagedBy   = "terraform"
      Environment = var.environment
    }
  }
}

default_tags-blokken er verdt å merke seg spesielt. Den sørger for at absolutt alle ressurser Terraform oppretter blir tagget automatisk, noe som gjør kostnadsrapportering og opprydding enormt mye enklere senere. Legg til variables.tf med regionen og miljønavnet:

variable "aws_region" {
  description = "AWS-region for ressursene"
  type        = string
  default     = "eu-north-1"
}

variable "environment" {
  description = "Miljønavn (dev, staging, prod)"
  type        = string
  default     = "dev"
}

Steg 5: Kjør terraform init

Nå er det på tide å initialisere prosjektet. Denne kommandoen laster ned AWS-provider-pluginen og setter opp en lokal .terraform-mappe.

terraform init

Forventet output ser slik ut:

Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 6.60"...
- Installing hashicorp/aws v6.60.0...
- Installed hashicorp/aws v6.60.0 (signed by HashiCorp)

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure.

Legg merke til at Terraform genererer en .terraform.lock.hcl-fil. Denne filen skal committes til Git, i motsetning til .terraform-mappen. Lock-filen sikrer at alle i teamet får nøyaktig samme providerversjon, ned til hash-nivå, uansett når de kjører init.

Steg 6: Bygg en VPC med subnett

Nå begynner selve infrastrukturen. Vi bygger et enkelt, men realistisk nettverksoppsett: én VPC, ett offentlig subnett, en internettgateway og en rutetabell. Legg dette i en ny fil, network.tf, for å holde ting oversiktlig.

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "${var.environment}-vpc"
  }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block               = "10.0.1.0/24"
  availability_zone        = "${var.aws_region}a"
  map_public_ip_on_launch  = true

  tags = {
    Name = "${var.environment}-public-subnet"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.environment}-igw"
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = "${var.environment}-public-rt"
  }
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

Legg merke til referansen aws_vpc.main.id. Dette er Terraforms deklarative styrke: du refererer til en annen ressurs sin fremtidige ID, og Terraform bygger automatisk en avhengighetsgraf som avgjør rekkefølgen ressursene opprettes i. Du trenger aldri å skrive "vent til VPC-en er ferdig før du lager subnettet" manuelt.

Steg 7: Legg til en sikkerhetsgruppe med minste privilegium

Ifølge Orca Securitys 2026-tall har 62 % av organisasjoner minst én åpen brannmurregel (0.0.0.0/0) i nettverkskonfigurasjon opprettet via IaC (kilde: Orca Security via Conversational Geek, 2026). Det skjer typisk fordi utviklere kopierer et eksempel fra internett uten å stramme inn kildeadressen. Vi gjør det riktig fra start: SSH-porten åpnes kun fra din egen IP.

variable "my_ip" {
  description = "Din offentlige IP for SSH-tilgang, i CIDR-format"
  type        = string
}

resource "aws_security_group" "web" {
  name        = "${var.environment}-web-sg"
  description = "Tillat HTTP fra internett og SSH kun fra egen IP"
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "HTTP fra internett"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "SSH kun fra min IP"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.my_ip]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.environment}-web-sg"
  }
}

Finn din egen offentlige IP med curl ifconfig.me og legg den til i terraform.tfvars som my_ip = "din.ip.her/32". Merk skråstrek-32, som betyr nøyaktig én IP-adresse, ikke et helt subnett.

Steg 8: Opprett en EC2-instans

Med nettverk og sikkerhetsgruppe på plass legger vi til selve serveren. Vi bruker en datakilde (data-blokk) for å slå opp nyeste Amazon Linux-image dynamisk, i stedet for å hardkode en AMI-ID som blir utdatert om noen måneder.

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

resource "aws_instance" "web" {
  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]

  root_block_device {
    volume_size = 8
    volume_type = "gp3"
    encrypted   = true
  }

  tags = {
    Name = "${var.environment}-web-server"
  }
}

Legg merke til encrypted = true på rotvolumet. Orca Securitys tall viser at 84 % av organisasjoner har minst én ukryptert lagringsressurs opprettet via IaC, ofte fordi kryptering ikke er standard i AWS-konsollet og glemmes i koden også. Ett linje kode her lukker det hullet permanent for alle fremtidige instanser som arver samme mønster.

Steg 9: Kjør terraform plan og les output nøye

Før du bygger noe, kjør planen. Dette er sikkerhetsnettet som skiller Terraform fra å klikke blindt i konsollet.

terraform plan -out=tfplan

Output vil vise en linje per endring, prefikset med et symbol: + betyr opprett, ~ betyr endre, - betyr slett, og -/+ betyr slett og gjenopprett. Med oppsettet over bør du se seks nye ressurser:

Plan: 6 to add, 0 to change, 0 to destroy.

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

Å lagre planen til en fil med -out er en vane du bør ta med deg videre. Det garanterer at apply utfører nøyaktig det du så i planen, uten risiko for at noen andre har endret AWS-miljøet i mellomtiden.

Steg 10: Kjør terraform apply

Nå kan du faktisk bygge infrastrukturen:

terraform apply "tfplan"

Fordi vi brukte en lagret plan-fil, ber ikke Terraform om bekreftelse igjen. Etter 30-60 sekunder skal du se:

aws_vpc.main: Creating...
aws_vpc.main: Creation complete after 2s [id=vpc-0a1b2c3d4e5f6g7h8]
aws_internet_gateway.main: Creating...
aws_subnet.public: Creating...
aws_security_group.web: Creating...
aws_internet_gateway.main: Creation complete after 1s
aws_subnet.public: Creation complete after 1s
aws_route_table.public: Creating...
aws_security_group.web: Creation complete after 2s
aws_route_table.public: Creation complete after 1s
aws_route_table_association.public: Creating...
aws_route_table_association.public: Creation complete after 1s
aws_instance.web: Creating...
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Creation complete after 24s [id=i-0123456789abcdef0]

Apply complete! Resources: 6 added, 0 changed, 0 destroyed.

Legg til en outputs.tf for å hente ut ting du faktisk trenger etterpå, som instansens offentlige IP:

output "instance_public_ip" {
  description = "Offentlig IP for web-serveren"
  value       = aws_instance.web.public_ip
}

output "vpc_id" {
  description = "ID for VPC-en som ble opprettet"
  value       = aws_vpc.main.id
}

Kjør terraform apply på nytt (Terraform oppdager at kun output-verdier er lagt til, ikke ny infrastruktur) og deretter terraform output for å se verdiene.

Steg 11: Flytt state til en sikker, delt backend

Så langt har state-filen ligget lokalt på disken din, som terraform.tfstate. Det fungerer for en tutorial, men er uegnet for alt som involverer mer enn én person eller mer enn én maskin. State-filen inneholder alt Terraform vet om infrastrukturen din, inkludert attributter som i praksis fungerer som hemmeligheter (databasepassord, private nøkler generert av providere, osv). Ifølge samme Orca-tall lagrer 68 % av organisasjoner hemmeligheter i klartekst i miljøvariabler knyttet til IaC-administrerte lagringskonfigurasjoner, et mønster som gjentar seg i ubeskyttede state-filer.

Løsningen er en S3-backend med kryptering og en låsemekanisme. Opprett en egen, minimal Terraform-konfigurasjon (gjerne i en separat mappe kalt bootstrap/) for å lage bøtta som skal holde state for hovedprosjektet:

resource "aws_s3_bucket" "tfstate" {
  bucket = "min-org-terraform-state-eu-north-1"

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_s3_bucket_versioning" "tfstate" {
  bucket = aws_s3_bucket.tfstate.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate" {
  bucket = aws_s3_bucket.tfstate.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "aws:kms"
    }
    bucket_key_enabled = true
  }
}

resource "aws_s3_bucket_public_access_block" "tfstate" {
  bucket                  = aws_s3_bucket.tfstate.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Fra og med Terraform 1.11 håndterer S3-backenden state-låsing internt uten en separat DynamoDB-tabell, slik eldre guider fortsatt anbefaler. Koble backenden til hovedprosjektet ved å legge dette til main.tf:

terraform {
  backend "s3" {
    bucket       = "min-org-terraform-state-eu-north-1"
    key          = "tutorial/terraform.tfstate"
    region       = "eu-north-1"
    encrypt      = true
    use_lockfile = true
  }
}

Kjør terraform init på nytt. Terraform spør om du vil kopiere eksisterende lokal state til den nye backenden. Svar ja, og slett deretter den lokale terraform.tfstate-filen manuelt for å unngå forvirring senere.

Steg 12: Rydd opp med terraform destroy

Når du er ferdig med å teste, fjern alt for å unngå unødvendig fakturering:

terraform destroy

Terraform viser en plan for hva som slettes og ber om en yes-bekreftelse skrevet ut i sin helhet, ikke bare "y". Dette er med vilje: det skal være vanskelig å slette produksjonsinfrastruktur ved et uhell. I team-sammenheng bør du dessuten sette prevent_destroy = true i en lifecycle-blokk på kritiske ressurser som databaser, slik at selv et vellykket destroy-kall stopper med en feilmelding for de ressursene.

Terraform vs. OpenTofu: hva lisensendringen betyr for deg

HashiCorp gikk over fra Mozilla Public License til Business Source License (BSL 1.1) i august 2023, og etter at IBM kjøpte HashiCorp har spørsmålet om lisens blitt enda mer aktuelt for nordiske virksomheter med strenge leverandørkrav. Under BSL kan du fritt bruke Terraform internt til å provisjonere egen infrastruktur, akkurat slik vi har gjort i denne guiden. Begrensningen gjelder først når du innebygger Terraform i et konkurrerende kommersielt produkt eller selger det som en administrert tjeneste, altså et scenario de aller fleste utviklerteam aldri havner i.

OpenTofu er den fullstendig åpen kildekode-forkede varianten, styrt gjennom Linux Foundation, med identisk HCL-syntaks og state-filformat. Ifølge Scalrs 2026-tall har OpenTofu rundt 12 % aktiv adopsjon blant IaC-praktikere per april 2026, med ytterligere 27 % av team som evaluerer eller planlegger å utvide bruken. Migrering mellom de to beskrives gjennomgående som teknisk enkel siden det meste av økosystemet, inkludert provider-kompatibiliteten, er delt. For de fleste team som kun bruker Terraform internt mot AWS, endrer ikke lisensen noe praktisk. For team som bygger et produkt rundt Terraform-motoren, eller som ønsker langsiktig leverandørnøytralitet, er OpenTofu verdt å teste (se opentofu.org).

Terraform vs. AWS CloudFormation: når velge hva

Et spørsmål som dukker opp i nesten enhver AWS-arkitekturdiskusjon er om man bør bruke Terraform eller AWS' eget CloudFormation. Begge er deklarative, begge har en plan-lignende funksjon (CloudFormation kaller det change sets), og begge kan håndtere komplekse avhengighetsgrafer. Forskjellen ligger i rekkevidde og eierskap. CloudFormation er bygget og vedlikeholdt av AWS selv, støtter nye AWS-tjenester som regel raskere enn tredjeparts-providere klarer å følge opp, og krever ingen egen state-håndtering siden AWS lagrer tilstanden for deg. Til gjengjeld er CloudFormation låst til AWS: den kan ikke administrere en Cloudflare-DNS-post eller en GitHub-repositoriinnstilling i samme stack.

Terraform sin styrke er nettopp det motsatte. Fordi provider-modellen er generisk, kan samme kodebase administrere AWS-ressurser, Cloudflare-oppsett og overvåkingsintegrasjon i Datadog i én og samme apply. For team som kun kjører i AWS og aldri forlater økosystemet, er CloudFormation (eller AWS CDK, som genererer CloudFormation under panseret) et fullt legitimt valg med mindre overhead siden dere slipper å tenke på state-backend i det hele tatt. For team med en flersky-strategi, eller som allerede har SaaS-verktøy utenfor AWS de vil administrere som kode, er Terraform som regel riktig vei.

EgenskapTerraformAWS CloudFormation
Sky-støtteAWS, Azure, GCP, Cloudflare, 4000+ providereKun AWS (og delvis tredjeparts ressurstyper)
State-håndteringEgen state-fil, du administrerer backendHåndteres automatisk av AWS
SpråkHCL (eller JSON)YAML eller JSON
Støtte for nye AWS-tjenesterKommer etter AWS-lansering, via providerutgivelseOfte tilgjengelig samtidig med tjenestelansering
LisensBusiness Source License (BSL) / OpenTofu er åpen kildekodeGratis AWS-tjeneste, ingen separat lisens
Egnet for flerskyJa, kjernebruksområdeNei, kun AWS

Del state mellom prosjekter med terraform_remote_state

Etter hvert som infrastrukturen vokser, deler de fleste team opp Terraform-koden i flere separate prosjekter, gjerne ett for nettverk, ett for databaser, og ett per applikasjon. Problemet som oppstår da er hvordan applikasjonsprosjektet finner ut hvilken VPC eller hvilket subnett det skal bruke, når de ble opprettet av et helt annet prosjekt med sin egen state-fil. Løsningen er datakilden terraform_remote_state, som leser output-verdier fra en annen states backend uten at prosjektene deler samme kodebase.

data "terraform_remote_state" "network" {
  backend = "s3"

  config = {
    bucket = "min-org-terraform-state-eu-north-1"
    key    = "tutorial/terraform.tfstate"
    region = "eu-north-1"
  }
}

resource "aws_instance" "app" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t3.small"
  subnet_id     = data.terraform_remote_state.network.outputs.vpc_id != "" ? data.terraform_remote_state.network.outputs.public_subnet_id : null
}

Dette mønsteret krever at nettverksprosjektet faktisk eksponerer de riktige verdiene som output, akkurat slik vi gjorde i steg 10 med instance_public_ip og vpc_id. En vanlig fallgruve er å glemme å oppdatere output-blokken når nye ressurser legges til i nettverksprosjektet, noe som gjør at applikasjonsprosjektet plutselig mangler data det trenger og feiler med en uklar "attribute not found"-feil under plan. Hold en enkel regel: enhver ressurs-ID et annet prosjekt kan tenkes å trenge, eksponeres som output med en gang den opprettes, ikke først når behovet dukker opp.

Test infrastrukturkoden med terraform test

Terraform har et innebygd testrammeverk der du skriver tester i .tftest.hcl-filer og kjører dem med terraform test. Dette lar deg validere at moduler oppfører seg som forventet før de brukes i produksjon, uten å måtte apply-e mot en ekte AWS-konto for hver eneste endring. Testene kan kjøre i to moduser: command = plan, som kun sjekker planen uten å opprette noe, og command = apply, som faktisk bygger ressurser midlertidig i et testmiljø og river dem ned igjen etterpå.

# tests/network.tftest.hcl
run "vpc_has_correct_cidr" {
  command = plan

  assert {
    condition     = aws_vpc.main.cidr_block == "10.0.0.0/16"
    error_message = "VPC CIDR-blokk matcher ikke forventet verdi"
  }
}

run "security_group_blocks_public_ssh" {
  command = plan

  assert {
    condition     = aws_security_group.web.ingress[1].cidr_blocks[0] != "0.0.0.0/0"
    error_message = "SSH-porten er åpen mot hele internett, forventet avgrenset IP"
  }
}

Kjør testene med terraform test fra prosjektroten. Dette er spesielt verdifullt for moduler som brukes av flere team internt, siden en regresjon i en delt modul ellers kan gå upåaktet hen til noen kjører apply i produksjon og oppdager at sikkerhetsgruppen plutselig er bredere åpen enn tiltenkt. Sett testkjøringen som et obligatorisk steg i CI-pipelinen, på linje med enhetstester for applikasjonskode.

Vanlige feil og fallgruver

De fleste Terraform-relaterte hendelser skyldes ikke feil i selve verktøyet, men vaner som sniker seg inn tidlig og blir dyre å rette senere. Her er fem feil vi ser gang på gang.

  • Hardkodede hemmeligheter i .tf-filer. Selv om filen aldri committes, havner verdien i klartekst i state-filen. Bruk alltid variabler markert sensitive = true, og hent faktiske hemmeligheter fra AWS Secrets Manager eller SSM Parameter Store i stedet for å skrive dem direkte.
  • Lokal state uten backend. Så snart mer enn én person jobber på samme prosjekt, må state ligge i en delt, kryptert og låst backend. Lokal state fører garantert til konflikter der to personer overskriver hverandres endringer.
  • Manglende versjonslåsing av provider. Uten required_providers-blokk med versjonsbegrensning kan et init på en annen maskin trekke inn en providerversjon med endret standardoppførsel, og planen blir plutselig annerledes enn forventet.
  • For brede IAM-rettigheter til Terraform-brukeren. Det er fristende å gi AdministratorAccess til CI/CD-brukeren "for å slippe å feilsøke rettigheter". Det gjør til gjengjeld en kompromittert pipeline til et fullstendig kontooverdrag. Bygg i stedet en policy avgrenset til nøyaktig de ressurstypene prosjektet trenger.
  • Manuelle endringer i AWS-konsollet etter apply. Så snart noen justerer en sikkerhetsgruppe manuelt i konsollet, oppstår drift: state-filen og virkeligheten er ikke lenger synkronisert. Neste plan vil enten prøve å reversere endringen automatisk, eller i verste fall feile kryptisk. Kjør terraform plan jevnlig for å fange drift tidlig, og lås ned konsolltilgang for ressurser som administreres av Terraform.

Feilsøking: de vanligste feilmeldingene og løsningene

Når noe går galt, er Terraforms feilmeldinger som regel presise nok til å peke deg mot roten. Her er de åtte feilene du mest sannsynlig vil støte på.

FeilmeldingVanligste årsakLøsning
Error: error configuring Terraform AWS Provider: no valid credential sources foundAWS-profil eller miljøvariabler er ikke sattKjør aws configure --profile terraform-tutorial på nytt og bekreft med aws sts get-caller-identity
Error: creating EC2 Instance: UnauthorizedOperationIAM-brukeren mangler nødvendige rettigheterLegg til manglende handlinger (f.eks. ec2:RunInstances) i IAM-policyen
Error acquiring the state lockEn annen prosess (eller en avbrutt kjøring) holder fortsatt låsenVent til den andre kjøringen er ferdig, eller bruk terraform force-unlock LOCK_ID hvis du er sikker på at ingen andre kjører
Error: InvalidAMIID.NotFoundAMI-ID er hardkodet og finnes ikke lenger i regionenBruk en data "aws_ami"-blokk med filter i stedet for hardkodet ID, slik vi gjorde i steg 8
Provider produced inconsistent final planEn bug i providerversjonen, eller en ressurs endret utenfor Terraform mens apply kjørteKjør terraform plan på nytt for å friske opp state, og oppgrader provideren hvis feilen vedvarer
Error: Cycle: aws_security_group, aws_instanceTo ressurser refererer til hverandre i en sirkelBryt sirkelen ved å fjerne én av referansene, ofte ved å bruke separate security group rules i stedet for inline-blokker
Backend configuration changedBackend-blokken ble endret uten migreringKjør terraform init -reconfigure eller -migrate-state avhengig av om du vil beholde gammel state
Error: creating S3 Bucket: BucketAlreadyExistsS3-bøttenavn er globalt unikt og allerede tatt av noen andreLegg til en unik prefiks, f.eks. organisasjonsnavn og region, i bøttenavnet

Avanserte tips for produksjonsbruk

Når du går fra tutorial til faktisk produksjon, er det noen mønstre som lønner seg fra dag én. Bruk Terraform-moduler for å pakke gjenbrukbare mønstre (som "standard VPC-oppsett" eller "standard EC2 med logging") slik at samme kode ikke kopieres på tvers av prosjekter. Terraform Registry har et offisielt AWS VPC-modul vedlikeholdt av HashiCorp-partnere som dekker de fleste standardbehov, og som ofte er tryggere enn å skrive nettverkslaget fra bunnen selv.

Sett opp arbeidsområder (terraform workspace) eller separate state-filer per miljø, slik at dev, staging og prod aldri deler samme state. Bland aldri disse. Legg CI/CD-pipelinen din opp til å kjøre terraform plan automatisk på hver pull request og poste resultatet som en kommentar, mens terraform apply kun kjører etter manuell godkjenning på hovedgrenen. Dette gir deg samme fire-øyne-prinsipp som kodegjennomgang, bare for infrastruktur.

Bruk til slutt terraform validate og et statisk analyseverktøy som tflint eller Checkov i pipelinen din før plan kjøres. Slike verktøy fanger opp nettopp mønstrene fra Orca-tallene over, som åpne sikkerhetsgrupper og ukrypterte volumer, før koden i det hele tatt når apply-steget.

Bygg en avgrenset IAM-policy for Terraform i CI/CD

Vi nevnte i fallgruvene at en CI/CD-bruker med AdministratorAccess er en av de farligste snarveiene i et Terraform-oppsett. Problemet er at det tar tid å finne ut nøyaktig hvilke IAM-handlinger et prosjekt trenger, så la oss gå gjennom hvordan du bygger policyen riktig fra start i stedet for å gi opp og bruke administratorrettigheter.

Den enkleste fremgangsmåten er å starte med en avgrenset policy som dekker nøyaktig ressurstypene fra denne guiden (VPC, EC2, sikkerhetsgrupper og S3 for state), og heller utvide den etter hvert som prosjektet vokser i stedet for å gi bred tilgang på forhånd. AWS CloudTrail kan i tillegg brukes til å se hvilke API-kall som faktisk ble avvist med AccessDenied, slik at du legger til nøyaktig de manglende handlingene fremfor å gjette.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "NetworkOgCompute",
      "Effect": "Allow",
      "Action": [
        "ec2:CreateVpc", "ec2:DeleteVpc", "ec2:DescribeVpcs",
        "ec2:CreateSubnet", "ec2:DeleteSubnet", "ec2:DescribeSubnets",
        "ec2:CreateInternetGateway", "ec2:AttachInternetGateway", "ec2:DetachInternetGateway",
        "ec2:CreateRouteTable", "ec2:CreateRoute", "ec2:AssociateRouteTable",
        "ec2:CreateSecurityGroup", "ec2:AuthorizeSecurityGroupIngress", "ec2:AuthorizeSecurityGroupEgress",
        "ec2:RunInstances", "ec2:TerminateInstances", "ec2:DescribeInstances",
        "ec2:DescribeImages", "ec2:CreateTags", "ec2:DescribeTags"
      ],
      "Resource": "*"
    },
    {
      "Sid": "StateBucketTilgang",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::min-org-terraform-state-eu-north-1",
        "arn:aws:s3:::min-org-terraform-state-eu-north-1/*"
      ]
    }
  ]
}

Legg merke til at policyen over kun dekker nettopp de ressurstypene vi har brukt i denne tutorialen. Et reelt produksjonsmiljø trenger typisk flere statement-blokker for RDS, IAM-roller, Lambda og lignende, men prinsippet er det samme: legg til handlinger etter behov, dokumenter hvorfor hver blokk finnes, og gå aldri veien om wildcard-handlinger som ec2:* med mindre du har en svært god grunn.

I GitHub Actions eller GitLab CI bør denne policyen kobles til en IAM-rolle som pipelinen påtar seg midlertidig via OpenID Connect (OIDC), i stedet for langvarige access keys lagret som hemmeligheter i CI-systemet. OIDC eliminerer risikoen for at en lekket nøkkel gir varig tilgang, siden hvert pipeline-kjøring får et kortvarig token som utløper automatisk etter kjøringen er ferdig.

Komplett prosjekt: alle filene samlet

Under følger den fullstendige filstrukturen for prosjektet vi har bygget gjennom denne guiden, slik at du kan kopiere den direkte inn i et nytt prosjekt.

terraform-aws-tutorial/
├── main.tf          # terraform-blokk, provider, backend
├── variables.tf     # aws_region, environment, my_ip
├── network.tf       # VPC, subnett, internet gateway, rutetabell
├── security.tf      # sikkerhetsgruppe
├── compute.tf       # EC2-instans og AMI-data-blokk
├── outputs.tf        # instance_public_ip, vpc_id
├── terraform.tfvars   # faktiske verdier (ikke committet)
├── .gitignore
└── .terraform.lock.hcl  # committes, sikrer reproduserbar providerversjon

Med denne strukturen kan du kjøre hele livssyklusen med tre kommandoer: terraform init for å hente providere, terraform plan -out=tfplan for å se endringer, og terraform apply "tfplan" for å utføre dem. Når testen er ferdig, terraform destroy rydder opp alt uten at du trenger å huske hva du opprettet i konsollet for tre uker siden.

Kostnadsstyring når infrastruktur er kode

En ofte oversett fordel med IaC er kostnadskontroll. Fordi hver ressurs er eksplisitt definert i kode, kan du kjøre kostnadsestimering (for eksempel med Infracost, som integreres direkte i Terraform-planen) før noe faktisk opprettes i AWS. Dette er spesielt nyttig i nordiske organisasjoner der FinOps-praksis har blitt en fast del av skyforvaltningen, og der uventede regninger fra glemte t3.large-instanser eller ubrukte NAT-gatewayer er en gjenganger i interne revisjoner.

Tagging, som vi satte opp automatisk med default_tags i steg 4, er selve grunnlaget for kostnadsallokering i AWS Cost Explorer. Uten konsekvent tagging er det tilnærmet umulig å svare på spørsmålet "hvilket team bruker mest penger på EC2 i eu-north-1", et spørsmål som garantert kommer opp i budsjettmøtet før eller siden.

Et annet grep som lønner seg raskt er å legge inn budsjettvarsler via aws_budgets_budget-ressursen direkte i samme Terraform-prosjekt som resten av infrastrukturen. Da får du varsling på e-post eller SNS så snart forbruket krysser en terskel, uten å måtte huske å sette det opp manuelt i konsollet hver gang et nytt miljø opprettes. For team som administrerer flere AWS-kontoer, gjør det samme prinsippet det mulig å standardisere kostnadskontroll på tvers av alle miljøer gjennom én delt Terraform-modul, i stedet for at hver kontoeier finner opp sin egen løsning.

Ofte stilte spørsmål

Må jeg betale for å bruke Terraform?
Nei. Terraform CLI er gratis å bruke for å provisjonere din egen infrastruktur, også kommersielt, under Business Source License. Du betaler kun hvis du bruker HashiCorps skytjeneste HCP Terraform utover gratisnivået, eller hvis du bygger et konkurrerende produkt rundt selve Terraform-motoren.

Bør jeg velge Terraform eller OpenTofu for et nytt prosjekt?
For de fleste team som kun bruker verktøyet internt mot AWS, spiller det liten praktisk rolle siden syntaks og state-format er identisk. Velg Terraform for det største økosystemet og flest ferdige moduler i Terraform Registry, eller OpenTofu hvis leverandørnøytralitet og fullstendig åpen kildekode er et krav i organisasjonen din.

Hva er forskjellen på terraform plan og terraform apply?
plan viser hvilke endringer Terraform vil gjøre uten å utføre dem, mens apply faktisk utfører endringene. Kjør alltid plan først, og les output nøye før du godkjenner en apply, spesielt i produksjon.

Kan jeg bruke Terraform sammen med AWS CloudFormation i samme konto?
Ja, men unngå å la begge verktøyene administrere de samme ressursene. Del opp ansvaret tydelig, for eksempel per konto, per VPC eller per team, slik at det alltid er entydig hvilket verktøy som "eier" en gitt ressurs.

Hvorfor bør state-filen aldri ligge i Git?
State-filen inneholder attributter fra hver ressurs Terraform administrerer, og disse kan inkludere passord, private nøkler og andre sensitive verdier i klartekst avhengig av ressurstype. En S3-backend med kryptering og streng tilgangskontroll er alltid tryggere enn å committe filen, selv i et privat repository.

Hva gjør jeg hvis noen har endret infrastruktur manuelt utenfor Terraform?
Kjør terraform plan for å se drift-rapporten. Terraform viser da forskjellen mellom det som faktisk finnes i AWS og det state-filen forventer. Du kan enten importere den manuelle endringen inn i konfigurasjonen med terraform import, eller la neste apply reversere endringen tilbake til det koden sier.

Hvilken AWS-region bør nordiske team bruke?
eu-north-1 (Stockholm) er den primære AWS-regionen for nordiske arbeidslaster, med lavest breddegradslatens for brukere i Norge, Sverige, Danmark og Finland. Mange team kombinerer den med eu-west-1 (Irland) eller eu-central-1 (Frankfurt) som sekundærregion for redundans.

Er Terraform egnet for små prosjekter, eller kun store organisasjoner?
Terraform skalerer begge veier. Selv et enkelt prosjekt med én EC2-instans, slik vi har bygget her, drar nytte av versjonskontroll, gjenskapbarhet og en tydelig plan før endringer skjer. Terskelen for å komme i gang er lav nok til at det er verdt det selv for hobbyprosjekter.