Enable deployment of client servers without public IPs using private network (10.0.0.0/16) with NAT gateway via edge server. ## Infrastructure Changes: ### Terraform (tofu/): - **network.tf**: Define private network and subnet (10.0.0.0/24) - NAT gateway route through edge server - Firewall rules for client servers - **main.tf**: Support private-only servers - Optional public_ip_enabled flag per client - Dynamic network block for private IP assignment - User-data templates for public vs private servers - **user-data-*.yml**: Cloud-init templates - Private servers: Configure default route via NAT gateway - Public servers: Standard configuration - **dns.tf**: Update DNS to support edge routing - Client domains point to edge server IP - Wildcard DNS for subdomains - **variables.tf**: Add private_ip and public_ip_enabled options ### Ansible: - **deploy.yml**: Add diun and kuma roles to deployment ## Benefits: - Cost savings: No public IP needed for each client - Scalability: No public IP exhaustion limits - Security: Clients not directly exposed to internet - Centralized SSL: All TLS termination at edge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| dns.tf | ||
| main.tf | ||
| network.tf | ||
| outputs.tf | ||
| README.md | ||
| terraform.tfvars.example | ||
| user-data-private.yml | ||
| user-data-public.yml | ||
| variables.tf | ||
| versions.tf | ||
| volumes.tf | ||
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