Terraform-managed infrastructure for FleetYards on Hetzner Cloud with DNS via DNSimple.
This repo provisions Hetzner Cloud servers (web + accessories), networking, firewalls, load balancers, and DNS records. Servers are configured via cloud-init and deployed with Kamal. State is stored in an S3-compatible backend (Hetzner Object Storage).
- Web servers: Public-facing, ports 80/443/22 open, run the application
- Accessory servers: Private, only reachable via ProxyJump through web servers, run databases/caches
- Load balancer: Created automatically when
web_servers_count > 1 - DNS: Managed via DNSimple (A records for root, www, api, admin, docs subdomains + short domains)
- Workspaces:
stage(fleetyards.dev) andlive(fleetyards.net) — workspace-driven config viaenv_configin variables.tf
| File | Purpose |
|---|---|
cloud.tf |
Core resources: servers, network, firewalls, load balancer |
dns.tf |
DNSimple DNS records |
data.tf |
Cloud-init data sources for server provisioning |
variables.tf |
Input variables and per-environment config (env_config) |
locals.tf |
Computed values (IP assignments) |
versions.tf |
Terraform version constraints, backend config, provider versions |
providers.tf |
Provider configuration (Hetzner, DNSimple, AWS/S3, Bunny CDN) |
secrets.tf |
1Password provider and data sources for secrets |
storage.tf |
S3-compatible object storage buckets (storage + backups) |
cdn.tf |
Bunny CDN pull zones and custom hostnames |
outputs.tf |
Server IPs and SSH config output |
cloudinit/ |
Cloud-init templates (base.yml, web.yml, accessories.yml) |
tests/ |
Terraform native tests (.tftest.hcl) |
# Initialize
terraform init
# Validate configuration
terraform validate
# Run tests
terraform test
# Plan changes (select workspace first)
terraform workspace select stage
terraform plan
# Apply changes
terraform apply
# Format all .tf files
terraform fmt- Test (
.github/workflows/terraform-test.yml): Runsterraform validateandterraform teston push to main and PRs - Deploy (
.github/workflows/terraform-deploy.yml):- Stage: Auto-deploys after successful test on main
- Live: Manual dispatch only, with destructive change detection — blocks if plan contains any deletes or replacements
- Secrets are managed via 1Password using the Terraform
onepasswordprovider - Data sources in
secrets.tffetch secrets from thefleetyards-infravault at plan/apply time - Locally: requires 1Password desktop app running or
op signin - CI: uses
OP_SERVICE_ACCOUNT_TOKENGitHub Secret to authenticate with 1Password - AWS S3 backend credentials remain in GitHub Secrets (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY) since the backend initializes before providers - Locally, AWS S3 backend credentials are loaded from a
.envfile (gitignored) .tfstatefiles are stored in the remote S3 backend.tfvarsfiles are gitignored — no secrets are committed to the repository
- Resource naming:
fltyrd-{workspace}-{role}[-{index}](e.g.,fltyrd-live-web-1) - Private network:
10.0.0.0/16, subnet10.0.0.0/24 - Web server IPs start at
10.0.0.2, accessory IPs follow sequentially - Server labels control firewall assignment (
ssh=yes,http=yes/no,env={workspace})
- The
livedeploy pipeline blocks destructive changes (deletes/replacements) automatically user_datachanges are ignored via lifecycle rules (servers won't be recreated for cloud-init changes)- Always run
terraform planbeforeterraform applylocally - When modifying
liveworkspace resources, prefer the CI pipeline over local applies - Never run
terraform destroyonliveworkspace without explicit confirmation - Never run
terraform applywithout reviewing the plan first
- Tests live in
tests/*.tftest.hcl single_server.tftest.hcl— validates single web + accessories setupmultiple_servers.tftest.hcl— validates multi-server + load balancer setup- Tests use mock providers to avoid requiring real API credentials
- Always run
terraform testafter making changes - Always run
terraform validatebefore committing - Always run
terraform fmtbefore committing
- Follow existing patterns in
cloud.tffor Hetzner resources - Use workspace-aware naming:
"fltyrd-${terraform.workspace}-{name}" - Add relevant labels for firewall rules
- If adding new providers, pin versions in
versions.tf - Add test coverage in
tests/for new resources