doran/deploy/hetzner/README.md

3.5 KiB
Raw Permalink Blame History

Hetzner single-node bootstrap

This repos canonical infrastructure path is:

  1. provision one Hetzner VM with Terraform
  2. let cloud-init install k3s (and optionally Tailscale)
  3. run scripts/hetzner/bootstrap.sh from the operator workstation
  4. apply repo-managed platform + project manifests
  5. bootstrap Forgejo, the runner, repo secrets/variables, and the first CI-driven deploy

Source of truth

Use these docs first:

  • docs/hetzner-k3s-bootstrap.md — bootstrap + destroy + required env
  • docs/hetzner-self-hosted-ci-runbook.md — Forgejo/runner/CI flow
  • docs/k8s-observability.md — Grafana, Loki, Promtail, Headlamp
  • deploy/k8s/README.md — Kubernetes layout
  • deploy/k8s/overlays/hetzner-single-node/README.md — overlay details

Current architecture

Infrastructure under infra/terraform/hetzner/ provisions:

  • one Hetzner VM
  • one firewall
  • one private network attachment
  • cloud-init for unattended k3s install

Kubernetes platform services deployed from this repo:

  • Forgejo
  • Forgejo runner
  • private registry
  • cert-manager
  • Traefik via k3s bundled ingress controller
  • Grafana
  • Loki
  • Promtail
  • Headlamp

Project services deployed from this repo:

  • Redpanda
  • near-intents-ingest
  • dummy-reactor
  • dummy-executor
  • dummy-consumer

Bootstrap model

The current bootstrap is workstation-driven after Terraform. cloud-init does not clone this repo onto the node.

scripts/hetzner/bootstrap.sh now:

  • loads config and secrets from scripts/hetzner/bootstrap-secrets.env
  • resolves *_PASS values through pass
  • runs Terraform
  • configures DNS through Cloudflare or Porkbun when credentials are present
  • fetches kubeconfig from the node
  • renders .state/hetzner/generated-overlay/
  • applies platform + project manifests
  • bootstraps Forgejo admin/user/repo/runner state
  • seeds the repo into Forgejo
  • lets Forgejo Actions perform the routine image build + deploy path by default

Legacy local-image bootstrap still exists, but the default/steady-state path is Forgejo Actions.

Required operator inputs

Create and source:

cp scripts/hetzner/bootstrap-secrets.env.example scripts/hetzner/bootstrap-secrets.env
source scripts/hetzner/bootstrap-secrets.env

At minimum you need:

  • Hetzner credentials
  • SSH public key path
  • public domain settings
  • registry credentials
  • app secret(s)
  • Forgejo admin credentials
  • Grafana admin credentials

Recommended:

  • Tailscale auth key for private admin/control-plane access
  • DNS provider credentials
  • pass-backed secret refs instead of raw env values

Current live/public surfaces

  • Forgejo: https://git.doran.133011.xyz/
  • Registry: https://registry.doran.133011.xyz/
  • Grafana: https://grafana.doran.133011.xyz/
  • Headlamp: https://headlamp.doran.133011.xyz/

Notes

  • The Forgejo runner no longer reads a pre-seeded runner_registration_token from a secret. Bootstrap generates a one-time token in-cluster and persists the runner config on the Forgejo PVC.
  • Registry auth is created imperatively during bootstrap from REGISTRY_USERNAME and REGISTRY_PASSWORD; manual overlay applies must provide registry.htpasswd themselves.
  • Headlamp login uses a generated Kubernetes service-account token; bootstrap stores it in pass when HEADLAMP_ADMIN_TOKEN_PASS is configured.
  • Ingress is Traefik-based. The old ingress-nginx path is obsolete.

Status

This path has been rebuilt successfully and the cluster is operational, but if you want the strongest reproducibility guarantee after any new platform addition, do one more full destroy/rebuild rehearsal.