Skip to content
Upgrade and roll back

Upgrade and roll back

Upgrade

Validate existing CRs against the new schema before installing new CRDs — admission rules tighten between releases and the check is offline (Validation ):

live=$(mktemp); kubectl get tailnet,tailnetegress,tailnetingress -A -o json > "$live"
LIVE_CR_FILE="$live" task test-validation; rm -f -- "$live"
  1. Apply the new CRDs. Helm installs CRDs from crds/ but never upgrades them: run task crd-upgrade KUBE_CONTEXT=<context> for a manual installation, or set install.crds: CreateReplace and upgrade.crds: CreateReplace on a Flux HelmRelease.
  2. helm upgrade (or apply the new deploy/). The operator re-rolls every agent whose image changed; agent Deployments use Recreate, so each affected CR’s data path is interrupted for the restart.
  3. kubectl get tailnet — COMPAT=Compatible, POLICY=InSync or Applied, DNS=CannotVerify.
  4. kubectl get tailnetegress,tailnetingress -A — every row READY=True; test one egress and one ingress connection.

A pure operator restart with unchanged desired state does not restart agents; a Ready=False during an agent image change is a real interruption, not a status flap. Breaking changes per version are

Verify after the upgrade

  1. Apply the new CRDs, then the Deployment (image + OPERATOR_IMAGE).
  2. The operator re-rolls agent Deployments whose image changed.
  3. Verify:
kubectl -n tailnet-system rollout status deploy/tailnet-operator
kubectl get tailnetegress,tailnetingress -A
# every row READY=True
kubectl get tailnet corp          # COMPAT=Compatible; POLICY=Applied/InSync; DNS=CannotVerify (manual)

Rollback

SituationAction
A render is wrong but not yet applied (DryRun)fix the base or the CR; nothing reached Headscale
Headscale rejected the render (PolicyApplied=False/PolicyRejected)the previous policy is still active; read the message, fix — the write is retried with backoff (15 s doubling to 1 min; transport failures to 5 min), a base/CR change retries at once
The operator must stop writingpolicy.mode: DryRun — the operator only renders from now on
Restore the Git policy on the serverkubectl -n tailnet-system exec deploy/headscale -- headscale policy set -f /acl/policy (mode DryRun first, or it is overwritten within 5 min)
Leave database modeHEADSCALE_POLICY_MODE=file, restart; the mounted file is authoritative again
Downgrade the operatorrestore both the previous container image and OPERATOR_IMAGE (agents also roll back); the newer CRDs and their admission rules remain installed
Relax the v0.3.0 admission rulesrestore the previous deploy/crds/ manifests through the same deployment mechanism; never delete the CRDs, which also deletes their CRs

Neither rollback restores a previously applied Headscale policy — switch to DryRun first if writes must stop. Keep the desired CR manifests in Git: an older schema prunes newer optional fields on write.

Source: docs/how-to/upgrade-rollback.md at v0.18.0 — found a mistake? Tell us.