Skip to content

Migrate VPS off IONOS (Hetzner plan)

Status: future plan — not started. Keep this page as the source of truth when revisiting cost / hosting.
Audience: humans + agents planning cutover.
Related: Products, domains & legal, Architecture overview, DNS, Woodpecker agents, Portfolio.

No execution yet

Do not provision, flip DNS, or change CI until Phase 0 inventory is done and a cutover window is agreed.

Goal

Keep the same platform (Forgejo, Woodpecker, Traefik, docs, Kuma, Netdata, DefectDojo, WearLab, …), spend less than IONOS after promo renewals, and scale later without jumping to AWS/GCP.

Locked decisions

Topic Choice
Target provider Hetzner Cloud (EU, predictable monthly pricing)
Topology Keep dual VPS — infra + apps (do not merge onto one box)
Portfolio Leave IONOS web hosting — static origin on infra VPS + Cloudflare edge
DNS Move zone to Cloudflare (free); domain registration may stay at IONOS short-term
Big cloud (AWS/GCP/Azure) Out of scope

Suggested starting sizes

Tune after measuring current IONOS RAM/CPU (Netdata / free -h):

Role Rough class Why
Infra Mid (≈4–8 vCPU, 8–16 GB) Forgejo + Woodpecker server + Traefik + docs + Kuma + Netdata parent + DefectDojo
Apps Smaller/mid (≈3–4 vCPU, 4–8 GB) WearLab + Traefik + room for 1–2 more apps
Backups Hetzner Storage Box (or B2) Off-site; today backups are mostly local-only on each VPS

Current map (today)

Piece Where Notes
Infra VPS IONOS 85.215.32.166 Platform subdomains
Apps VPS IONOS 82.165.177.110 wearlab, future apps
Portfolio IONOS web hosting Woodpecker SFTP to public_html
DNS IONOS All A records — see DNS

Woodpecker apps agent reaches infra gRPC via SSH tunnel (IONOS only allows 22/80/443 between VPS). Plan the same pattern on Hetzner unless ports are deliberately opened.

flowchart TB
  dns[DNS today IONOS]
  ionosWeb[IONOS web hosting portfolio]
  infra[Infra VPS 85.215.32.166]
  apps[Apps VPS 82.165.177.110]
  dns --> infra
  dns --> apps
  dns --> ionosWeb
  ci[Woodpecker] --> infra
  ci -->|SFTP| ionosWeb
  ci -->|SSH agent| apps

Target map (after cutover)

flowchart TB
  cf[Cloudflare DNS free]
  infraH[Hetzner infra VPS]
  appsH[Hetzner apps VPS]
  cf --> infraH
  cf --> appsH
  portfolio[Portfolio static on infra]
  infraH --> portfolio
  ci2[Woodpecker] --> infraH
  ci2 -->|SSH deploy| appsH
  ci2 -->|rsync or SSH to infra| portfolio

Portfolio: leave IONOS hosting?

Yes (recommended).

Option Pros Cons
Keep on IONOS hosting Familiar; isolated from VPS moves Extra bill; weaker TLS; second deploy path
Move to infra + Cloudflare Drop a line item; one deploy style; better edge TLS/HTTP3 Small static load on infra

Domain registration alone is cheap; the expensive parts are usually VPS renewals and web hosting.

Phases

Phase 0 — Inventory (zero downtime)

  • [ ] List IONOS invoice lines (2× VPS, web hosting, domain, add-ons)
  • [ ] Per VPS: disk use, RAM peaks, Docker stacks, Forgejo/volume sizes
  • [ ] Locate SOPS age key, Woodpecker secrets, SSH keys, Traefik ACME data
  • [ ] List every DNS name to move (git, ci, docs, wearlab, apex, www, …)
  • [ ] Grep hardcoded IPs (85.215.32.166, 82.165.177.110) in secrets, Kuma, scripts, local SSH
  • [ ] Freeze: no new permanent DNS during migration week
  • [ ] Agree cutover window; open Plane ticket when Plane is healthy

Phase 1 — Provision Hetzner (old stack stays live)

  • [ ] Create infra + apps servers; firewall 22/80/443
  • [ ] deploy user + SSH keys (reuse existing bootstrap patterns)
  • [ ] Optional: floating IPs
  • [ ] Storage Box; test write/read backup
  • [ ] Do not point public DNS yet

Phase 2 — Infra “dark” (private verify)

  • [ ] Bootstrap /opt/devops on new infra
  • [ ] Restore Forgejo + Woodpecker data/config
  • [ ] Bring up Traefik, docs, Kuma, Netdata parent, DefectDojo
  • [ ] Keep Plane off until stable (ENABLE_PLANE_STACK=false)
  • [ ] Private checks: clone from new Forgejo; test pipeline
  • [ ] Public git / ci still on old IONOS

Phase 3 — Apps “dark”

  • [ ] Bootstrap Traefik + /opt/apps on new apps VPS
  • [ ] Deploy WearLab (existing secrets / run-deploy.sh)
  • [ ] Rebuild Woodpecker apps-agent tunnel to new infra
  • [ ] Private Host-header verify for wearlab
  • [ ] Temporary Kuma monitor; do not flip public DNS yet

Phase 4 — Portfolio off IONOS web hosting

  • [ ] Static origin on infra (Traefik/nginx vhost for apex/www)
  • [ ] Change portfolio CI: SFTP → SSH/rsync to infra (same idea as docs deploy)
  • [ ] Cloudflare: orange-cloud apex + www; SSL Full / Full (strict) when origin ready
  • [ ] Point apex/www away from IONOS web hosting
  • [ ] After 1–2 stable weeks: cancel IONOS web hosting

Phase 5 — Public DNS cutover

Prep: Cloudflare zone, TTL 60–300s, full record checklist from DNS.

Suggested order:

  1. App hosts (wearlab, …) — easy rollback
  2. Low-risk platform (docs, status, metrics, security)
  3. git + ci — short pipeline freeze, then flip

Also:

  • [ ] Update Woodpecker deploy_ssh_host, apps SSH secrets, Kuma, Netdata tunnel, local SSH aliases
  • [ ] Keep old IONOS VPS powered ~7 days for rollback
  • [ ] Update docs (overview, DNS, products-and-brands, onboarding) with new IPs

Phase 6 — Decommission IONOS compute

  • [ ] Final Forgejo + volume backup to Storage Box; test restore once
  • [ ] Power off old VPS → cancel
  • [ ] Confirm web hosting already cancelled
  • [ ] Optional later: transfer domain to Cloudflare Registrar

Scaling after the move

Stay on Hetzner VPS; grow in layers — do not rewrite onto big cloud for this workload.

If this hurts Then do this
Infra RAM / CI slow Vertical resize infra
WearLab / app CPU Resize apps, then split a heavy app to its own CX
Many small apps Keep multi-app on apps VPS + Traefik (current model)
CI queue backlog Extra small agent box, or more agent capacity
Static sites Cloudflare cache in front of infra (portfolio + docs)
Disk / disaster fear Daily Storage Box + restic; quarterly restore test
Multi-region / compliance Only then managed DB or second region — not now

Anti-patterns: one mega shared VPS “to save money”; Kubernetes; full AWS migration for this stack.

Risks

  • Secrets/monitors still pointing at old IPs
  • Let’s Encrypt rate limits if DNS is flipped repeatedly (Cloudflare proxy helps)
  • Woodpecker agent tunnel must be rebuilt to new infra
  • Don’t block the whole migration on Plane being up

Before execution — answer these

  1. Rough monthly IONOS total (or product list)
  2. Confirm Hetzner (vs Contabo/Netcup) — this plan assumes Hetzner
  3. Preferred cutover window
  4. Whether apex must be near-zero downtime

Explicitly out of scope (for now)

  • Implementing CI/template changes in this repo until Phase 4 is scheduled
  • Buying extra domains for hosting
  • Collapsing infra + apps onto a single VPS