Terraform — zarządzanie infrastrukturą VPS jako kod
Opublikowano: 9 kwietnia 2026 · Kategoria: VPS / DevOps
Klikanie w panelu dostawcy działa dla jednego serwera. Przy dwóch serwerach, firewallach, rekordach DNS i backupach zaczyna się chaos. Terraform rozwiązuje ten problem: opisujesz całą infrastrukturę w plikach tekstowych, wersjonujesz w git i odtwarzasz jedną komendą. Infrastructure as Code (IaC) to standard w każdym profesjonalnym projekcie VPS.
Instalacja Terraform
Terraform to pojedynczy binarny plik. Instalacja na Ubuntu/Debian:
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 # Terraform v1.7.x
Struktura projektu Terraform
Typowy projekt Terraform dla VPS wygląda tak:
infra/ ├── main.tf # Główne zasoby (serwery, sieci) ├── variables.tf # Deklaracje zmiennych ├── outputs.tf # Wartości wyjściowe (IP, ID) ├── versions.tf # Wymagane wersje providerów ├── terraform.tfvars # Wartości zmiennych (NIE commituj jeśli zawiera tokeny!) └── .terraform.lock.hcl # Lock file providerów (commituj)
Provider Hetzner Cloud
Hetzner to popularny wybór dla polskich projektów — serwery w Niemczech (Frankfurt, Nuremberg), niskie ceny i oficjalny provider Terraform. Konfiguracja:
# versions.tf
terraform {
required_version = ">= 1.5"
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.47"
}
}
}
provider "hcloud" {
token = var.hcloud_token
} # variables.tf
variable "hcloud_token" {
description = "Hetzner Cloud API Token"
type = string
sensitive = true # Nie pojawi się w logach
}
variable "server_type" {
description = "Typ serwera Hetzner (cx11, cx21, cx31...)"
type = string
default = "cx22"
}
variable "location" {
description = "Lokalizacja serwera"
type = string
default = "nbg1" # Nuremberg
} # main.tf — VPS na Hetzner
resource "hcloud_ssh_key" "default" {
name = "my-ssh-key"
public_key = file("~/.ssh/id_rsa.pub")
}
resource "hcloud_server" "web" {
name = "web-01"
image = "ubuntu-22.04"
server_type = var.server_type
location = var.location
ssh_keys = [hcloud_ssh_key.default.id]
labels = {
environment = "production"
role = "web"
}
# Cloud-init: podstawowa konfiguracja przy starcie
user_data = <<-EOT
#cloud-config
package_update: true
packages:
- nginx
- ufw
runcmd:
- ufw allow 22
- ufw allow 80
- ufw allow 443
- ufw --force enable
EOT
}
# Firewall
resource "hcloud_firewall" "web" {
name = "web-firewall"
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = ["0.0.0.0/0", "::/0"]
}
rule {
direction = "in"
protocol = "tcp"
port = "80"
source_ips = ["0.0.0.0/0", "::/0"]
}
rule {
direction = "in"
protocol = "tcp"
port = "443"
source_ips = ["0.0.0.0/0", "::/0"]
}
}
resource "hcloud_firewall_attachment" "web" {
firewall_id = hcloud_firewall.web.id
server_ids = [hcloud_server.web.id]
} # outputs.tf
output "server_ip" {
description = "Publiczne IP serwera"
value = hcloud_server.web.ipv4_address
}
output "server_id" {
description = "ID serwera Hetzner"
value = hcloud_server.web.id
} Provider DigitalOcean
DigitalOcean (Droplets) ma dojrzały provider z szerokim wsparciem dla Load Balancerów, Spaces (S3-compatible), Managed Databases i Kubernetes. Konfiguracja analogiczna do Hetzner:
# versions.tf — DigitalOcean
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.36"
}
}
}
provider "digitalocean" {
token = var.do_token
}
resource "digitalocean_droplet" "web" {
image = "ubuntu-22-04-x64"
name = "web-01"
region = "fra1" # Frankfurt
size = "s-1vcpu-1gb"
ssh_keys = [digitalocean_ssh_key.default.fingerprint]
}
resource "digitalocean_ssh_key" "default" {
name = "my-key"
public_key = file("~/.ssh/id_rsa.pub")
} Cykl życia — plan, apply, destroy
# 1. Inicjalizacja (jednorazowo lub po zmianie providera) terraform init # 2. Walidacja składni terraform validate # 3. Plan — co zostanie zrobione (NIE wykonuje zmian) terraform plan # 4. Apply — wykonaj zmiany (pyta o potwierdzenie) terraform apply # Lub bez pytania (CI/CD): terraform apply -auto-approve # 5. Sprawdź outputy terraform output server_ip # 6. Zniszcz wszystko (OSTROŻNIE!) terraform destroy
Zarządzanie state — remote backend
Domyślnie state zapisywany jest lokalnie w terraform.tfstate. W teamie lub
CI/CD użyj remote backend — np. S3 (AWS) lub Terraform Cloud:
# backend.tf — S3 remote state
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/vps/terraform.tfstate"
region = "eu-central-1"
encrypt = true
dynamodb_table = "terraform-locks" # State locking
}
} | Backend | Zalety | Kiedy używać |
|---|---|---|
| local | Zero konfiguracji | Projekty osobiste, nauka |
| S3 + DynamoDB | Locking, szyfrowanie, tanie | Projekty AWS, CI/CD |
| GCS | Wbudowany locking | Projekty GCP |
| Terraform Cloud | UI, runs, sentinel policies | Teamy, enterprise |
Zmienne i .tfvars
# terraform.tfvars (NIE commituj do gita jeśli zawiera tokeny!) hcloud_token = "your-hetzner-api-token" server_type = "cx22" location = "nbg1" # Lub przez zmienne środowiskowe (bezpieczniejsze dla tokenów): export TF_VAR_hcloud_token="your-hetzner-api-token" terraform apply
Best practices
- Zawsze sprawdzaj plan przed apply — szczególnie gdy widzisz
-/+(replace): Terraform zniszczy i odtworzy zasób, co może oznaczać utratę danych. - Trzymaj state w remote backend — lokalny state + kilka osób w teamie = konflikty i korupcja state.
- Używaj
sensitive = truedla tokenów i haseł — nie pojawią się w logach plan/apply. - Moduły Terraform — dla powtarzalnych zasobów (np. "serwer z firewallem i DNS")
twórz moduły w katalogu
modules/. Reużywalność i DRY. - Lifecycle — prevent_destroy — chroni krytyczne zasoby (bazy danych) przed przypadkowym
terraform destroy: dodaj bloklifecycle { prevent_destroy = true }.