Skip to content

Instantly share code, notes, and snippets.

@bogorad
Last active January 14, 2026 13:54
Show Gist options
  • Select an option

  • Save bogorad/f4c9005fd1c4100c78db9871345f9bfb to your computer and use it in GitHub Desktop.

Select an option

Save bogorad/f4c9005fd1c4100c78db9871345f9bfb to your computer and use it in GitHub Desktop.
reproducible infrastructure-as-code solution for secure, self-updating Hugo website server

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.

Core Architecture

  1. Terraform (Infrastructure Layer)

    • Provider: hcloud (Hetzner) and cloudflare.
    • Compute: CX23 VPS (2 vCPU, 4GB RAM) in Nuremberg (nbg1).
    • Security:
      • Pre-configured SSH keys (referenced by name from Hetzner project).
      • Dedicated hcloud_firewall attached to the server allowing only TCP ports 22, 80, and 443.
    • DNS:
      • Lookup existing Cloudflare zone by domain name (from secrets).
      • Create A records 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-exec script checking dig) before finishing, so Ansible doesn't fail on cert issuance.
  2. 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) with no_log: true.
    • TLS/Certificates:
      • Certbot: Use certbot-dns-cloudflare plugin.
      • 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).
    • 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, MKCOL allowed. create_full_put_path on for recursive directory creation.
        • Behavior: Uploads immediately overwrite live content.
      • Redirects: www CNAME behavior handled by Nginx redirecting www.website.comwebsite.com (or vice versa, effectively canonicalizing to one).

End of task


Evaluation Points for the Discussion

  • Secrets Handling: Check if they strictly follow the "memory only" rule for SOPS. Using ansible.builtin.copy with content= 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_resource with triggers is the standard pattern).
  • WebDAV Security: Verify they block PUT/DELETE on 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.

Constructed Response

I have prepared the discussion points below based on our refined requirements.

Project: Hetzner VPS with WebDAV-Enabled Hugo Site

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.

1. Infrastructure (Terraform)

  • 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).
  • 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-exec loop checking dig) to ensure DNS propagation before Ansible runs, preventing Certbot failures.
    • Idempotency: Re-running Terraform should succeed without modifying correctly configured records.

2. Configuration (Ansible)

  • 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: certbot with the certbot-dns-cloudflare plugin.
    • Method: DNS-01 challenge (using the Cloudflare API token).
    • Scope: Wildcard certificate covering website.com and *.website.com.
    • Renewal: Auto-renewal must be active (systemd timer/cron).
  • 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-data to ensure immediate read access.
      • Methods: Allow PUT, DELETE, MKCOL. Enable create_full_put_path for subdirectory support.
    • Traffic Logic:
      • Redirect www.website.comwebsite.com.
      • Enforce HTTPS.

3. Deliverables & Handoff

  • Repo: Complete Terraform + Ansible code.
  • Documentation:
    • Secrets: Explanation of .sops.yaml and how to rotate keys.
    • WebDAV: Connection details (URL: https://website.com/upload) and credentials.
    • Usage: Simple "how to deploy" guide (e.g., make deploy or step-by-step commands).
  • Verification:
    • An index.html (e.g., "Ready for Upload") placed by Ansible to verify connectivity.
    • The designer's subsequent upload will overwrite this file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment