Skip to content

Ops

Infrastructure-as-prose for the GrinSystem cloud VMs. This will migrate to Terraform once the surface stabilises; until then the YAML and Markdown here is the source of truth and is intentionally structured to map 1:1 to the corresponding hcloud Terraform resources.

Documents

  • Host baseline — admin user, SSH hardening, unattended-upgrades, ufw, fail2ban, hostname, banner, swap, and Docker prune cron. Applied to every VM before any application services land on it.
  • Coolify user — dedicated coolify service account for Coolify Cloud (SaaS) remote deployment: docker-group membership, passwordless sudo, SaaS pubkey + human emergency-access pubkey.
  • Network — private network topology, VM IP assignments, and the service-binding convention.
  • Firewall — Hetzner Cloud Firewall rules in Terraform-shaped YAML, cross-referenced with the host-level ufw baseline.
  • DB access — how developers reach internal services (Postgres, Redis, OTel collector) from a laptop via SSH tunnelling.

Provider

All non-prod infrastructure (dev, uat) lives on Hetzner Cloud in the EU Central network zone (Falkenstein / Nuremberg). The non-prod domain is grintest.pl, served via Cloudflare DNS.

Conventions

  • One firewall per environment. Each VM gets the firewall matching its environment (grintest-dev-public, future grintest-uat-public, …).
  • Default-deny inbound. Hetzner Cloud Firewall is default-deny; we only declare what is allowed.
  • Outbound is open. Locking down outbound on dev breaks too many legitimate things (apt updates, image pulls, Let's Encrypt validation, telemetry). Revisit for prod.
  • Defence in depth. The host also runs ufw with the same allow-list, so a single misconfiguration at either layer does not expose the VM.
  • Internal services bind to the private IP only. Postgres, Redis, Kafka (if reintroduced), and the OTel collector listen on the VM's private IP (10.0.1.x), never on 0.0.0.0. Bind by IP rather than interface name — interface names are hardware-dependent (see network.md). External access is via SSH tunnel — see db-access.md.