 Autor: [Monika Wojciechowska](/autorzy/monika-wojciechowska) Specjalistka SEO i treści webowych · Zweryfikowano Kwiecień 2026

1.  [Strona główna](/) ›
2.  [Baza wiedzy](/baza-wiedzy/) ›
3.  Terraform Modules i Remote State

# Terraform Modules i Remote State — organizacja dużej infrastruktury

Opublikowano: 10 kwietnia 2026 · Kategoria: VPS / DevOps

Gdy infrastruktura rośnie powyżej kilku serwerów, płaski plik `main.tf` staje się niemożliwy do utrzymania. Terraform modules rozwiązują ten problem analogicznie jak funkcje w programowaniu — enkapsulują logikę, przyjmują inputs i zwracają outputs. Remote state eliminuje konflikty przy pracy zespołowej. Ten artykuł pokazuje jak budować modularną infrastrukturę Terraform od prostych modułów VPS po zaawansowane setupy z Terragrunt.

## Tworzenie modułu — struktura i konwencje

Moduł to po prostu katalog z plikami .tf. Konwencja nakazuje trzy główne pliki: `main.tf` (zasoby), `variables.tf` (inputs) i `outputs.tf` (exports). Oto przykładowy moduł tworzący VPS na Hetzner Cloud:

\# modules/hetzner-vps/variables.tf
variable "server\_name" {
  description = "Nazwa serwera"
  type        = string
}

variable "server\_type" {
  description = "Typ serwera (cx11, cx21, cx31...)"
  type        = string
  default     = "cx21"
}

variable "location" {
  description = "Lokalizacja datacenter"
  type        = string
  default     = "nbg1"
  validation {
    condition     = contains(\["nbg1", "fsn1", "hel1"\], var.location)
    error\_message = "Nieprawidłowa lokalizacja. Dozwolone: nbg1, fsn1, hel1."
  }
}

variable "ssh\_keys" {
  description = "Lista nazw kluczy SSH z Hetzner Cloud"
  type        = list(string)
}

variable "tags" {
  description = "Mapa tagów dla serwera"
  type        = map(string)
  default     = {}
}

# modules/hetzner-vps/main.tf
locals {
  # Locals — obliczone wartości wewnętrzne modułu
  all\_tags = merge(
    {
      "managed-by" = "terraform"
      "module"     = "hetzner-vps"
    },
    var.tags
  )
}

resource "hcloud\_server" "main" {
  name        = var.server\_name
  server\_type = var.server\_type
  image       = "ubuntu-22.04"
  location    = var.location
  ssh\_keys    = var.ssh\_keys
  labels      = local.all\_tags

  lifecycle {
    ignore\_changes = \[ssh\_keys\]
  }
}

resource "hcloud\_firewall" "main" {
  name = "${var.server\_name}-firewall"

  rule {
    direction = "in"
    port      = "22"
    protocol  = "tcp"
    source\_ips = \["0.0.0.0/0", "::/0"\]
  }

  rule {
    direction = "in"
    port      = "80"
    protocol  = "tcp"
    source\_ips = \["0.0.0.0/0", "::/0"\]
  }

  rule {
    direction = "in"
    port      = "443"
    protocol  = "tcp"
    source\_ips = \["0.0.0.0/0", "::/0"\]
  }
}

resource "hcloud\_firewall\_attachment" "main" {
  firewall\_id = hcloud\_firewall.main.id
  server\_ids  = \[hcloud\_server.main.id\]
}

# modules/hetzner-vps/outputs.tf
output "server\_id" {
  description = "ID serwera"
  value       = hcloud\_server.main.id
}

output "server\_ip" {
  description = "Publiczny adres IPv4"
  value       = hcloud\_server.main.ipv4\_address
}

output "server\_ipv6" {
  description = "Publiczny adres IPv6"
  value       = hcloud\_server.main.ipv6\_address
}

## Wywoływanie modułu z konfiguracji głównej

\# main.tf — wywołanie modułu
module "web\_server" {
  source = "./modules/hetzner-vps"

  server\_name = "web-prod-01"
  server\_type = "cx21"
  location    = "nbg1"
  ssh\_keys    = \["moj-klucz", "klucz-ci"\]
  tags = {
    "environment" = "production"
    "role"        = "web"
  }
}

module "db\_server" {
  source = "./modules/hetzner-vps"

  server\_name = "db-prod-01"
  server\_type = "cx31"   # Większy dla bazy danych
  location    = "fsn1"
  ssh\_keys    = \["moj-klucz"\]
  tags = {
    "environment" = "production"
    "role"        = "database"
  }
}

# Używanie outputs modułu w innych zasobach
output "web\_server\_ip" {
  value = module.web\_server.server\_ip
}

# Ansible inventory generowany przez Terraform
resource "local\_file" "ansible\_inventory" {
  content = templatefile("${path.module}/templates/inventory.tmpl", {
    web\_ip = module.web\_server.server\_ip
    db\_ip  = module.db\_server.server\_ip
  })
  filename = "${path.module}/../ansible/inventory/production"
}

## Remote State — S3 + DynamoDB locking

Remote state w S3 z lockowaniem przez DynamoDB to najpopularniejsza konfiguracja dla AWS. Dla innych cloudów używasz analogicznych rozwiązań (GCS, Azure Blob, MinIO dla on-premise). Inicjalizacja backendu musi być wykonana raz:

\# backend.tf — konfiguracja remote state
terraform {
  backend "s3" {
    bucket         = "moja-firma-terraform-state"
    key            = "prod/web/terraform.tfstate"
    region         = "eu-central-1"
    dynamodb\_table = "terraform-state-lock"
    encrypt        = true

    # Opcjonalne: niestandardowe endpointy (MinIO, DigitalOcean Spaces)
    # endpoint       = "https://fra1.digitaloceanspaces.com"
    # skip\_region\_validation = true
    # force\_path\_style = true
  }

  required\_providers {
    hcloud = {
      source  = "hetznercloud/hcloud"
      version = "~> 1.45"
    }
  }
}

# Tworzenie zasobów S3 i DynamoDB (jednorazowo, własną konfiguracją)
# aws s3 mb s3://moja-firma-terraform-state --region eu-central-1
# aws s3api put-bucket-versioning \\
#   --bucket moja-firma-terraform-state \\
#   --versioning-configuration Status=Enabled
# aws dynamodb create-table \\
#   --table-name terraform-state-lock \\
#   --attribute-definitions AttributeName=LockID,AttributeType=S \\
#   --key-schema AttributeName=LockID,KeyType=HASH \\
#   --billing-mode PAY\_PER\_REQUEST \\
#   --region eu-central-1

# Inicjalizacja backendu
# terraform init

## Workspaces — izolacja środowisk

\# Zarządzanie workspaces
terraform workspace list          # Wylistuj workspaces
terraform workspace new staging   # Utwórz workspace
terraform workspace select prod   # Przełącz na workspace
terraform workspace show          # Pokaż aktualny workspace
terraform workspace delete staging  # Usuń (wymaga pustego state)

# Użycie workspace w konfiguracji
locals {
  env\_config = {
    prod = {
      server\_type = "cx31"
      count       = 3
    }
    staging = {
      server\_type = "cx11"
      count       = 1
    }
    dev = {
      server\_type = "cx11"
      count       = 1
    }
  }
  current = local.env\_config\[terraform.workspace\]
}

module "web\_servers" {
  count  = local.current.count
  source = "./modules/hetzner-vps"

  server\_name = "${terraform.workspace}-web-${count.index + 1}"
  server\_type = local.current.server\_type
  ssh\_keys    = var.ssh\_keys
}

## Terragrunt — DRY konfiguracja dla wielu modułów

Terragrunt eliminuje powtarzanie bloku backend w każdym module. Struktura katalogów odzwierciedla środowiska i serwisy:

\# Struktura projektu z Terragrunt
# infra/
# ├── terragrunt.hcl          # Root — wspólna konfiguracja
# ├── prod/
# │   ├── env.hcl             # Zmienne środowiska
# │   ├── web-servers/
# │   │   └── terragrunt.hcl  # Konfiguracja serwisów
# │   └── database/
# │       └── terragrunt.hcl
# └── staging/
#     └── web-servers/
#         └── terragrunt.hcl

# Root terragrunt.hcl
locals {
  env\_vars = read\_terragrunt\_config(find\_in\_parent\_folders("env.hcl"))
  env      = local.env\_vars.locals.environment
}

generate "backend" {
  path      = "backend.tf"
  if\_exists = "overwrite\_terragrunt"
  contents = <<-EOF
    terraform {
      backend "s3" {
        bucket         = "moja-firma-tf-state"
        key            = "${path\_relative\_to\_include()}/terraform.tfstate"
        region         = "eu-central-1"
        dynamodb\_table = "terraform-state-lock"
        encrypt        = true
      }
    }
  EOF
}

# prod/web-servers/terragrunt.hcl
terraform {
  source = "../../../modules/hetzner-vps"
}

include "root" {
  path = find\_in\_parent\_folders()
}

inputs = {
  server\_name = "web-prod-01"
  server\_type = "cx31"
  ssh\_keys    = \["prod-key"\]
}

# Uruchomienie wszystkich modułów naraz
# cd infra/prod
# terragrunt run-all plan
# terragrunt run-all apply

## Porównanie strategii organizacji infrastruktury

Strategia

Skala

Zalety

Wady

Pojedynczy main.tf

1-5 zasobów

Prosta, szybka do startu

Nie skaluje się

Moduły lokalne

5-50 zasobów

Reużywalność, DRY

Bez wersjonowania modułów

Rejestr publiczny

Dowolna

Gotowe rozwiązania, przetestowane

Zależność zewnętrzna

Workspaces

2-3 środowiska

Prosta izolacja środowisk

Ta sama konfiguracja w każdym ws

Terragrunt

50+ zasobów, wiele środowisk

DRY backend, run-all, dependencies

Dodatkowe narzędzie do nauki

## Najczęstsze pytania

Co to jest moduł Terraform i dlaczego warto go używać? +

Moduł Terraform to zestaw plików .tf zgrupowanych w katalogu, które realizują konkretną funkcjonalność (np. tworzenie VPS z firewallem i DNS). Zamiast kopiować te same zasoby między projektami, piszesz moduł raz i wywołujesz go wielokrotnie z różnymi parametrami. Moduły mają inputs (variables), outputs i locals — to jak funkcje w programowaniu. Zmniejszają duplikację kodu, ułatwiają standaryzację i umożliwiają reużywalność między środowiskami (dev/staging/prod).

Czym jest remote state w Terraform i dlaczego jest konieczny? +

Remote state to przechowywanie pliku terraform.tfstate w zdalnym backendzie (S3, GCS, Terraform Cloud, Azure Blob) zamiast lokalnie. Jest konieczny przy pracy zespołowej, bo lokalne tfstate blokuje współpracę (kto ma plik ten może deployować). Remote state + DynamoDB locking zapobiega równoczesnemu uruchomieniu terraform apply przez dwóch deweloperów. Dodatkowo zdalne state zawiera backup i audit log zmian infrastruktury.

Jaką różnicę robią workspaces w Terraform? +

Workspace to izolowana instancja state dla tej samej konfiguracji Terraform. Możesz mieć workspace default (prod), staging, dev — każdy z własnym tfstate. Przełączasz przez \`terraform workspace select staging\`. Wadą jest to że workspaces w tym samym katalogu oznaczają tę samą konfigurację — przy różnicach między środowiskami lepiej użyć terragrunt lub osobnych katalogów per środowisko. Workspaces sprawdzają się przy identycznej infrastrukturze różniącej się tylko nazwami/rozmiarami.

Co to jest Terragrunt i kiedy warto go używać? +

Terragrunt to wrapper nad Terraform eliminujący powtórzenia konfiguracji backendu i providerów między modułami. Zamiast kopiować blok backend w każdym module, definiujesz go raz w root terragrunt.hcl, a child konfiguracje dziedziczą. Terragrunt dodaje też funkcje: generate (generuje pliki .tf), dependency (referencje do outputs innych modułów), run-all (uruchamia terraform w wielu katalogach naraz). Warto używać przy infrastrukturze 10+ modułów.

Jak działają outputs modułu i jak z nich korzystać? +

Outputs to wartości eksportowane z modułu dla rodzica lub innych modułów. Definiujesz je w outputs.tf wewnątrz modułu (output "server\_ip" { value = hcloud\_server.main.ipv4\_address }). W module nadrzędnym odczytujesz przez module.nazwa\_modulu.server\_ip. Outputs mogą być też sensitive = true dla haseł. Dzięki outputs moduły mogą się komponować: moduł bazy danych eksportuje connection\_string, który moduł aplikacji otrzymuje jako input.

## Sprawdź oferty pasujące do tego scenariusza

Poniżej masz szybkie przejścia do ofert i stron z kodami rabatowymi tam, gdzie są dostępne.

Contabo

VPS do Terraform IaC — zarządzaj infrastrukturą jako kod

VPS + IaC

[Aktywuj rabat →](/out/contabo)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/contabo)

ProSerwer.pl

Polski VPS do automatyzowanego provisioningu przez Terraform

Polski VPS

[Aktywuj rabat →](/out/proserwer-pl)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/proserwer)

Mikr.us

Tani VPS do nauki Terraform i ćwiczeń IaC

Dev/Test

[Aktywuj rabat →](/out/mikrus)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/mikrus)

## Powiązane strony

-   [Terraform — zarządzanie infrastrukturą VPS jako kod](/baza-wiedzy/terraform-vps-infrastruktura)
-   [Ansible Roles i Galaxy — tworzenie i używanie ról](/baza-wiedzy/ansible-roles-galaxy)
-   [GitHub Actions: deployment na VPS — pełny workflow SSH](/baza-wiedzy/github-actions-deployment-vps)
-   [Wszystkie artykuły](/baza-wiedzy/)