The candidate's task is to build a reproducible infrastructure-as-code solution where Terraform provisions a Hetzner VPS and Cloudflare DNS, and Ansible configures a secure, self-updating Hugo website server.
-
Terraform (Infrastructure Layer)
- Provider:
hcloud(Hetzner) andcloudflare. - Compute:
CX23VPS (2 vCPU, 4GB RAM) in Nuremberg (nbg1). - Security:
- Pre-configured SSH keys (referenced by name from Hetzner project).
- Dedicated
hcloud_firewallattached to the server allowing only TCP ports 22, 80, and 443.
- DNS:
- Lookup existing Cloudflare zone by domain name (from secrets).
- Create
Arecords for@and*(wildcard) pointing to the VPS IP. - Output public IP and FQDNs.
- Idempotency: Ensure re-runs handle existing records gracefully.
- Propagation: Wait for DNS propagation (e.g., via
local-execscript checkingdig) before finishing, so Ansible doesn't fail on cert issuance.
- Provider:
-
Ansible (Configuration Layer)
- OS: Ubuntu 22.04/24.04 (LTS).
- Secrets:
- SOPS with age: Controller decrypts secrets in memory using age keys derived from SSH ed25519 keys.
- No temp files: Decrypted secrets (Cloudflare token, WebDAV password) are passed directly to tasks/templates (using
content: "{{ my_secret }}"or environment vars) withno_log: true.
- TLS/Certificates:
- Certbot: Use
certbot-dns-cloudflareplugin. - Validation: DNS-01 challenge (requires Cloudflare API token with DNS:Edit scope).
- Scope: Wildcard certificate (
*.website.com+website.com) to cover both apex and www. - Automation: Configure auto-renewal (systemd/cron).
- Certbot: Use
- Web Server (Nginx):
- Public Site: Served at
/from/var/www/website.com/public. - WebDAV: Served at
/upload(mapped to/var/www/website.com/public).- Auth: Basic Auth (user/pass from SOPS).
- Permissions: Owned by
www-data:www-data. - Methods:
PUT,DELETE,MKCOLallowed.create_full_put_path onfor recursive directory creation. - Behavior: Uploads immediately overwrite live content.
- Redirects:
wwwCNAME behavior handled by Nginx redirectingwww.website.com→website.com(or vice versa, effectively canonicalizing to one).
- Public Site: Served at
End of task
- Secrets Handling: Check if they strictly follow the "memory only" rule for SOPS. Using
ansible.builtin.copywithcontent=is safe; writing a decrypted file to/tmp/is not. - Idempotency: Ask how they handle the "wait for DNS" step in Terraform—does it block every time, or only on creation? (A
null_resourcewithtriggersis the standard pattern). - WebDAV Security: Verify they block
PUT/DELETEon the root path/and only allow it on/upload, or if they rely on the Basic Auth challenge to protect the whole root if accessed via WebDAV methods. - Permissions: Ensure the WebDAV upload user (
www-data) and the Nginx serve user (www-data) are aligned so uploads are immediately readable.
This covers the entire "what needs to be done" and "how to verify it." You are ready to discuss.
I have prepared the discussion points below based on our refined requirements.
Objective: Create a fully automated "VPS template" that provisions infrastructure on Hetzner and Cloudflare, then configures a secure web server where a designer can publish a Hugo site directly via WebDAV.
- Providers:
hcloud(Hetzner),cloudflare. - Compute:
- Server:
CX23(2 vCPU, 4GB RAM) in Nuremberg (nbg1). - OS: Ubuntu (LTS preferred, e.g., 24.04).
- Access: Use existing SSH keys pre-configured in the Hetzner project (data source lookup).
- Server:
- Network & Security:
- Firewall: Create a new Hetzner Cloud Firewall for this server allowing only 22/TCP (SSH), 80/TCP (HTTP), and 443/TCP (HTTPS).
- DNS (Cloudflare):
- Zone: Look up the Zone ID dynamically using the domain name (provided via secrets).
- Records: Create A records for the apex (
@) and wildcard (*) pointing to the VPS IPv4. - Wait Strategy: Implementation must include a wait step (e.g.,
local-execloop checkingdig) to ensure DNS propagation before Ansible runs, preventing Certbot failures. - Idempotency: Re-running Terraform should succeed without modifying correctly configured records.
- Secrets Management:
- Tool: SOPS with age (keys derived from SSH ed25519 keys).
- Constraint: In-memory only. Decrypted secrets (Cloudflare tokens, WebDAV passwords) must never be written to temporary files on disk. Use environment variables or direct content injection with
no_log: true.
- TLS / Certificates:
- Tool:
certbotwith thecertbot-dns-cloudflareplugin. - Method: DNS-01 challenge (using the Cloudflare API token).
- Scope: Wildcard certificate covering
website.comand*.website.com. - Renewal: Auto-renewal must be active (systemd timer/cron).
- Tool:
- Web Server (Nginx):
- Root: Serves Hugo
public/directory (e.g.,/var/www/site/public). - WebDAV:
- Endpoint:
/upload(mapped to the document root). - Auth: HTTP Basic Auth (hardcoded user/pass from SOPS).
- Permissions: Files owned by
www-data:www-datato ensure immediate read access. - Methods: Allow
PUT,DELETE,MKCOL. Enablecreate_full_put_pathfor subdirectory support.
- Endpoint:
- Traffic Logic:
- Redirect
www.website.com→website.com. - Enforce HTTPS.
- Redirect
- Root: Serves Hugo
- Repo: Complete Terraform + Ansible code.
- Documentation:
- Secrets: Explanation of
.sops.yamland how to rotate keys. - WebDAV: Connection details (URL:
https://website.com/upload) and credentials. - Usage: Simple "how to deploy" guide (e.g.,
make deployor step-by-step commands).
- Secrets: Explanation of
- Verification:
- An
index.html(e.g., "Ready for Upload") placed by Ansible to verify connectivity. - The designer's subsequent upload will overwrite this file.
- An