Completed: - ✅ Hetzner Cloud provider configuration - ✅ VPS server provisioning with for_each pattern - ✅ Cloud firewall rules (SSH, HTTP, HTTPS) - ✅ SSH key management - ✅ Outputs for Ansible dynamic inventory - ✅ Variable structure and documentation - ✅ Test server successfully provisioned Deferred: - DNS configuration (commented out, waiting for domain) Files added: - tofu/versions.tf - Provider versions - tofu/variables.tf - Input variable definitions - tofu/main.tf - Core infrastructure resources - tofu/dns.tf - DNS configuration (optional) - tofu/outputs.tf - Outputs for Ansible integration - tofu/terraform.tfvars.example - Configuration template - tofu/README.md - Comprehensive setup guide Closes #1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
4.8 KiB
4.8 KiB
OpenTofu Configuration for Hetzner Cloud
This directory contains Infrastructure as Code using OpenTofu to provision VPS instances on Hetzner Cloud.
Quick Start
1. Prerequisites
- OpenTofu installed (
brew install opentofu) - Hetzner Cloud account
- Domain registered and added to Hetzner DNS
2. Get Hetzner API Tokens
Cloud API Token:
- Go to https://console.hetzner.cloud/
- Select your project (or create one)
- Navigate to Security → API tokens
- Click Generate API token
- Name:
infrastructure-provisioning - Permissions: Read & Write
- Copy the token (shown only once!)
DNS API Token:
- Go to https://dns.hetzner.com/
- Click on your account name → API Tokens
- Click Create access token
- Name:
infrastructure-dns - Copy the token
Note
: You can use the same token for both if it has the necessary permissions.
3. Add Your Domain to Hetzner DNS
- Go to https://dns.hetzner.com/
- Click Add new zone
- Enter your domain (e.g.,
platform.nl) - Update your domain registrar's nameservers to:
hydrogen.ns.hetzner.comoxygen.ns.hetzner.comhelium.ns.hetzner.de
4. Configure OpenTofu
Create terraform.tfvars from the example:
cd tofu
cp terraform.tfvars.example terraform.tfvars
Edit terraform.tfvars with your values:
hcloud_token = "YOUR_ACTUAL_HETZNER_CLOUD_TOKEN"
hetznerdns_token = "YOUR_ACTUAL_HETZNER_DNS_TOKEN"
# Your SSH public key (e.g., from ~/.ssh/id_ed25519.pub)
ssh_public_key = "ssh-ed25519 AAAA... user@hostname"
# Your domain registered in Hetzner DNS
base_domain = "your-domain.com"
# Start with one test client
clients = {
test = {
server_type = "cx22" # 2 vCPU, 4 GB RAM - €6.25/month
location = "fsn1" # Falkenstein, Germany
subdomain = "test" # Will create test.your-domain.com
apps = ["zitadel", "nextcloud"]
}
}
enable_snapshots = true
5. Initialize OpenTofu
tofu init
This downloads the Hetzner provider plugins.
6. Plan Infrastructure
tofu plan
Review what will be created:
- SSH key resource
- Firewall rules
- VPS server(s)
- DNS records (A, AAAA, wildcard)
7. Apply Configuration
tofu apply
Type yes when prompted. This will:
- Upload your SSH key to Hetzner
- Create firewall rules
- Provision VPS instance(s)
- Create DNS records
8. View Outputs
tofu output
Shows:
- Client IP addresses
- FQDNs
- Complete client information
Server Sizes
| Type | vCPU | RAM | Disk | Price/month | Use Case |
|---|---|---|---|---|---|
| cx22 | 2 | 4 GB | 40 GB | €6.25 | Small clients (1-10 users) |
| cx32 | 4 | 8 GB | 80 GB | €12.50 | Medium clients (10-50 users) |
| cx42 | 8 | 16 GB | 160 GB | €24.90 | Large clients (50+ users) |
Locations
fsn1- Falkenstein, Germanynbg1- Nuremberg, Germanyhel1- Helsinki, Finland
Important Files
terraform.tfvars- GITIGNORED - Your secrets and configurationversions.tf- Provider versionsvariables.tf- Input variable definitionsmain.tf- Server and firewall resourcesdns.tf- DNS record managementoutputs.tf- Output values for Ansible
Adding a New Client
Edit terraform.tfvars and add to the clients map:
clients = {
existing-client = { ... }
new-client = {
server_type = "cx22"
location = "fsn1"
subdomain = "newclient"
apps = ["zitadel", "nextcloud"]
}
}
Then run:
tofu plan # Review changes
tofu apply # Provision new server
Removing a Client
Remove the client from terraform.tfvars, then:
tofu plan # Verify what will be destroyed
tofu apply # Remove server and DNS records
Warning: This permanently deletes the server. Ensure backups are taken first!
State Management
OpenTofu state is stored locally in terraform.tfstate (gitignored).
For production with multiple team members, consider:
- Remote state backend (S3, Terraform Cloud, etc.)
- State locking
- Encrypted state storage
Troubleshooting
"Zone not found" error
- Ensure your domain is added to Hetzner DNS
- Wait for DNS propagation (can take 24-48 hours)
- Verify zone name matches exactly (no trailing dot)
SSH key errors
- Ensure
ssh_public_keyis the public key content - Format:
ssh-ed25519 AAAA... commentorssh-rsa AAAA... comment - No newlines or extra whitespace
API token errors
- Ensure Read & Write permissions
- Check token hasn't expired
- Verify correct project selected in Hetzner console
Next Steps
After provisioning:
- SSH to server:
ssh root@<server-ip> - Run Ansible configuration:
cd ../ansible && ansible-playbook playbooks/setup.yml - Applications will be deployed via Ansible