Files
runbooks/cloudron-server-migration.md
T
inference-bot eec9572a3f Add Cloudron server migration runbook (Hostinger → Infomaniak)
Pre-flight, ordered cutover, post-restore verification, rollback, and the list of what Cloudron's backup does not cover. Written from a live inventory of the server on 2026-10-29; open unknowns are marked rather than guessed.
2026-10-29 22:46:40 +00:00

139 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Cloudron server migration — Hostinger → Infomaniak
Pre-flight and cutover runbook for moving the co-op's Cloudron server. Written
2026-10-29 from a live inventory of the server. **Owner:** Nathan. **Executor:** whoever
is at the console that weekend — assume it is not the person who wrote this.
The server being migrated hosts `my.inference.coop` and **16 apps**. The agent host is a
separate machine (already on Infomaniak) and is NOT part of this migration.
## 1. What Cloudron's backup covers — verified, not assumed
- All 16 apps and their `/app/data` (localstorage), plus addons: postgres, mysql,
mongodb, redis.
- Cloudron platform config: domains, dashboard users, app env vars, TLS config, backup
target settings, scheduled tasks.
- **The container registry app, 3.8 GB of custom images** (`librechat`, `litellm`,
`admin-panel`, `member-portal`, `member-dashboard`). These exist nowhere else — if the
registry does not restore, the custom apps cannot start.
- Size check that confirms app data is included: ~5.8 GB of app data visible inside
containers vs ~4.8 GB per backup run.
Apps: chat, chat-legacy (OpenWebUI), inference.coop (Surfer), forum (Loomio), matrix
(Synapse), gateway (LiteLLM), panel, registry, git (Gitea), vault (Vaultwarden), portal,
search (SearXNG), dashboard, newsletter (Listmonk), email (Cloudron Mail), cloud
(Nextcloud).
## 2. What it does NOT cover
- **The host OS layer.** Ubuntu itself, netplan, cloud-init, `sshd` config,
`authorized_keys`, hostname, PTR, provider firewall, hand-edited `/etc`. Build the new
box; do not expect to restore it.
- **The Cloudron licence/subscription** — must be moved to the new server.
- **DNS records.** Manual at yoursrs.com (domains.coop NS). Cloudron cannot manage them.
- **The backup target config and passphrase** — chicken-and-egg by design.
- **App data written to a host path instead of `/app/data`** — see the audit in §3.
- **Anything on the old box outside Cloudron** (e.g. `/etc` tweaks). Diff before
decommissioning.
Not a gap, despite looking like one: our custom images (registry is an app, backed up),
and the agent's own state (different machine).
## 3. Pre-flight
**Blocker — do this first.** The Vault app (`vault.inference.coop`) runs *on the server
being migrated*, and the backup passphrase is believed to live there. The credentials
needed to restore are inside the thing being restored.
- [ ] Export the Vault contents and store them off-server (personal password manager or
offline media). Confirm the **Cloudron backup passphrase** is in your own hands.
- [ ] Confirm the backup target's S3 access key is available off-box. The agent host has
`~/.hermes/security/backup_sites_backup.json` (0600) — check whether it carries the
key; if not, retrieve it from the Infomaniak console.
- [ ] New host capacity: `df -h`, `free -m`, `lsb_release -a`, `cloudron --version`.
Must hold ~6 GB of app data plus Docker images and growth; check swap exists.
- [ ] OS: same or newer Ubuntu than the old box. Cloudron install version must be ≥ the
old box's (9.0.4).
- [ ] Confirm the Cloudron subscription can be moved to a new server.
- [ ] Ask Infomaniak to set **rDNS/PTR for the new IP to `my.inference.coop`** — mail
deliverability depends on it.
- [ ] Open on the new box: 22, 80, 443, 465, 587, 993. Match the old box's exposure
(`ufw` is inactive there; Cloudron manages its own iptables — do not add ufw rules
that fight Docker).
- [ ] **Mount audit on the Cloudron host** — the one class of data the backup cannot see:
sudo docker ps -q | xargs -I{} sudo docker inspect {} \
--format '{{.Name}} {{range .Mounts}}{{.Source}}=>{{.Destination}} {{end}}' \
| tr ' ' '\n' | grep -v /app/data
A container showed `/var/lib/nextcloud` mounted from the host. Identify its owner and
size; if it holds real data, copy it separately.
- [ ] Record the highest backup run number before the freeze.
- [ ] Lower all A-record TTLs to 300 s **a day early** (currently 760–1642 s ≈ 13–27 min).
- [ ] List external allowlists that carry our IP: Regolo, GreenPT, Tinfoil, any mail relay,
and inbound webhooks. Update after cutover.
- [ ] Announce the window in `#updates`.
## 4. Cutover, in order
1. **Freeze writes.** Pause the portal's `membership_sweep` and `rag_refresh`, hold
Listmonk campaigns. Chat cannot be frozen — note the exact snapshot time.
2. **Final backup on the old box.** Run "Backup now" and confirm state `normal`.
Everything after this timestamp is *lost* in a forward migration.
3. **Stop the old box's backup schedule** (Settings → Backups → disable). Two Cloudrons
writing one target interleave and produce `EntityTooLarge` quota failures.
4. **Build the new box and restore** from the Infomaniak S3 target, using the passphrase
from §3. Expect TLS to fail until DNS moves — reach the dashboard by IP or a temporary
override.
5. **Switch the A records** to the new IP.
6. Wait out the TTL, then confirm certificates issued.
7. **Re-enable backups on the new box — once daily, retention 7 days.** (The approved
change was never applied; the API has no route for it, so do it in the dashboard.)
8. Update external allowlists. Leave the old box running read-only.
## 5. Post-restore verification
- [ ] Dashboard loads with a valid certificate; all 16 apps `running`.
- [ ] **One custom app loads** — e.g. `panel.inference.coop`. This is the proof the
registry restored; without it nothing custom starts.
- [ ] Data spot-checks: Matrix login + a room loads; Gitea repo list; portal
`/admin/overview` returns the expected member count; gateway `/v1/models`.
- [ ] Mail: send to and receive from an external address; MX resolves to the new IP; SPF
still `a:my.inference.coop`; DKIM keys survived (they are in app data); PTR correct.
- [ ] Federation: `curl -s https://matrix.inference.coop/_matrix/federation/v1/version`.
Other homeservers cache the old IP and will retry it for a while — that is expected.
- [ ] Agent API access: `cloudron list` from the agent host. The API token lives in
Cloudron's config so it should survive; if it does not, mint a new one and update the
agent's `.env`.
- [ ] Schedules fire **once**, not twice: portal sweeps, Listmonk, agent digests.
- [ ] First backup on the new box is `normal` and in the expected size range.
## 6. Rollback
- Keep the old box powered on, read-only, for several days.
- Rollback = point DNS back. Do not delete old snapshots from S3 until the new box has at
least one verified backup run.
- Know the asymmetry: anything written between the final backup and the DNS switch exists
**only on the old box**. Rolling forward discards it; rolling back keeps it. Keep that
window short and quiet.
## 7. Open unknowns — resolve before cutover
- Which app owns the `/var/lib/nextcloud` host mount, and how much data is in it.
- The Cloudron host's OS version, disk and RAM (not visible over the API).
- Whether the S3 access key is in the off-box JSON or only inside Cloudron.
- Whether any provider API key is bound to our current IP.
- Whether the subscription permits a server move.
## 8. Pitfalls this server has already produced
- **Quota failures:** 4.82 GB per run twice daily hit the Infomaniak ceiling
(`Upload exceeds quota`). Retention is 7 days; schedule must be once daily.
- **The once-a-day change cannot be made over the API** — dashboard only. Historically it
was reported as done and was not.
- **`cloudron update --image` reads the manifest from the current directory.** Running it
from the wrong repo fails with `manifest id does not match`, which looks like a server
problem and is not.
- **The agent host is not migrated** but will see errors while DNS moves. It needs no
config change: every reference is a hostname, including `--server my.inference.coop`.