Upgrading
Upgrading Planeon means moving your PLANEON_VERSION forward, applying any
new migrations, and rolling the services onto the new images. The order
below is not a suggestion — each step depends on the one before it, and
skipping or reordering them is how deployments end up serving traffic
against a schema they don't understand.
The order (never reorder)
-
Back up. See Backup and restore — take a fresh database dump, and confirm your
SECRETS_MASTER_KEYand.envare backed up alongside it. Do this before touching anything else; it's your only way back if the upgrade goes wrong. -
Bump
PLANEON_VERSIONin.envto the release you're upgrading to. -
Pull the new images:
docker compose pull -
Apply migrations explicitly:
docker compose run --rm migrate -
Gate: confirm the schema is current before rolling anything.
docker compose run --rm migrate statusEvery migration must report
Applied. Do not proceed to step 6 until it does — re-run step 4 and check again if anything is pending. -
Roll the services:
docker compose up -dThis recreates the five version-pinned services —
api,worker,gateway,guacd, andweb— on the new images (postgresandredisare untouched;caddyruns a fixed upstreamcaddy:2-alpineimage, so aPLANEON_VERSIONbump does not recreate it — it only changes if you edit its own image or config;migratestays a one-shot that doesn't run as part ofup).Live desktop sessions drop during the upgradeRecreating
guacdtears down its process, which terminates every live desktop connection tunnelled through it (RDP, VNC, SSH). Expect active sessions to disconnect during the upgrade window; users reconnect once the newguacdis up. Schedule the upgrade accordingly, or drain sessions first if a hard cutover isn't acceptable.
Why this order
The api and worker each embed the newest migration version they expect at
build time, and each refuses to report ready — /readyz returns 503
naming migrations — until the database schema is at or above that
version (see Observability → Readiness
semantics).
Migrating before rolling the service images means the schema is already
current by the time the new containers start, so there's no window where a
freshly rolled service sits not ready waiting on a migration you haven't
run yet — and no window where a load balancer or orchestrator routes traffic
to a container that isn't actually ready to serve it.
Migrations are also never applied automatically by api or worker at
startup, upgrade or otherwise. That's a deliberate platform invariant, not
an oversight: it keeps schema changes visible and auditable as a distinct,
observable step, and it avoids multiple replicas racing to migrate the same
database concurrently. You run docker compose run --rm migrate yourself,
every time, and the migrate status gate is how you confirm it actually
happened before you move on.
Rollback
Planeon's migrations are roll-forward only — there are no
down-migrations in production, and applied migration files are append-only
(CI rejects a pull request that edits or deletes one). There is no
migrate down step to reach for.
If an upgrade needs to be undone, roll back by restoring your pre-upgrade
backup: stop the services, restore the database dump you took in step 1
(see Backup and restore →
Restore procedure),
and redeploy the previous PLANEON_VERSION. This is exactly why the backup
in step 1 above is not optional — it's the only rollback path.
Multi-worker deployments
If you run more than one worker replica (docker compose up -d --scale worker=N), upgrading them is safe with no special coordination. Each
worker's job leases carry a deadline; if a worker is stopped mid-upgrade
while holding a lease, any surviving worker reclaims the expired lease once
it passes and requeues the job for another worker to pick up (handlers are
idempotent, so a retry from the start is always safe) — jobs are never
silently dropped, and row-level locking (FOR UPDATE SKIP LOCKED) keeps two
workers from ever running the same job twice. You can upgrade worker
replicas one at a time or all together with the same docker compose up -d
in step 6.