Upgrade notes for 1.0
These notes cover moving to 1.0.0 from a prerelease or a local development
build. 1.0 is the first supported release; there is no supported upgrade from an
older tagged release because none exists.
If you are installing fresh, follow the installation guide instead — you do not need this document.
Before you upgrade
- Back up the database. Run
scripts/backup.shand confirm the dump completes. See backup and restore for the backup and restore procedure. - Note your current versions. Record the image tags (or the commit) you are
upgrading from, so you can roll back by re-pinning
PRAXIS_VERSION. - Check the recordings volume ownership. Interactive sessions now require a working recording. See Breaking change: session recording is mandatory.
- Read the known limitations at the bottom of this document before committing to the upgrade.
Upgrade steps
-
Pin the target version. In
.env:Terminal window PRAXIS_VERSION=1.0.0 -
Pull (or build) the 1.0.0 images.
Terminal window # Pull published images (--profile proxy starts Caddy, the browser ingress):docker compose -f docker-compose.yml -f docker-compose.prod.yml --profile bundled --profile proxy pull# Or, to build locally / for air-gapped installs:docker compose -f docker-compose.yml -f docker-compose.prod.yml --profile bundled --profile proxy up -d --build -
Apply database migrations. The backend runs migrations on start, but you can apply them explicitly against the new image:
Terminal window docker compose exec -T backend alembic upgrade head -
Reconcile lifecycle / EOL reference data. A fresh database loads this via migration; an existing database may have drifted from the shipped seed:
Terminal window docker compose exec -T backend python -m app.scripts.update_eol_data# Confirm nothing is left pending:docker compose exec -T backend python -m app.scripts.update_eol_data --dry-run# -> summary should report "0 new, 0 pruned" -
Confirm the supported worker posture. Keep
UVICORN_WORKERS=1(the default) while browser interactive SSH sessions are enabled. The production entrypoint refuses to start with more than one worker unless you explicitly set the unsupportedALLOW_UNSAFE_MULTIWORKER_SESSIONS=1override. -
Verify health. Confirm the services report healthy:
Terminal window docker compose -f docker-compose.yml -f docker-compose.prod.yml --profile bundled --profile proxy ps# -> backend and frontend should be "healthy" -
Run the smoke gates (see safe upgrade for the full set): fresh-install / upgrade / backup-restore smokes and the demo walkthrough if you want a visual confirmation of the lifecycle story.
Breaking change: privileged access baseline
Praxis 1.0 ships no standing user-facing privileged escalation — no Praxis-issued root shell, no password-sudo path, no break-glass root profile, no raw sudoers authoring, and no sudo inheritance. The upgrade migration corrects the launch-incompatible defaults that earlier versions seeded:
- Every fleet role’s raw
sudoers_snippetis cleared, and privileged OS groups (wheel/sudo/root/admin) are stripped from every role — built-in and custom. The built-inadminandmaintainerroles no longer carryALL=(ALL) NOPASSWD:ALL. Raw sudoers text is not preserved as dormant config; if you genuinely need the prior policy text, recover it from your pre-upgrade database backup. - The fleet-role API now rejects any request that sets a raw sudoers snippet or a privileged OS group.
Clearing the database is not enough — you must reconcile hosts. Any
/etc/sudoers.d/praxis-<login> drop-in already deployed by a pre-1.0 release
stays on the host until reconciliation removes it. The migration flags every live
managed account for privilege reconciliation; run a fleet reconcile after
upgrading to remove the on-host drop-ins:
docker compose exec -T backend python -c \ "from app.db.session import SessionLocal; from app.services import fleet_reconciliation_service as f; \ db=SessionLocal(); print(f.reconcile_pending_privilege(db)); db.close()"# -> {'provisioned': N, 'removed': .., 'errors': E, 'hosts': H, 'still_pending': P}Any host that is unreachable stays flagged (and is surfaced as error /
unreconciled) and is retried on the next reconcile — the stale sudo privilege is
never silently left live. Check what is still outstanding with
fleet_reconciliation_service.privilege_reconcile_status(db), which reports the
count of hosts/accounts still pending drop-in removal. Interactive root on managed
hosts is now out-of-band under your ops runbook, not a Praxis-issued grant.
Breaking change: session recording is mandatory
An interactive browser SSH session is opened only once its recording is running.
If the recording cannot start, the session is refused, and its row in the session
ledger is marked errored with the reason recording_unavailable. Praxis no
longer falls back to an unrecorded shell, because a shell with no recording is an
audit gap rather than a degraded feature.
The backend image now pre-creates /data/praxis/recordings owned by the backend
user (UID/GID 1000), so Docker gives a newly initialized recordings_data volume
the correct ownership. That applies only when Docker initializes the volume. If
your recordings_data volume already holds recordings and is owned by root,
the ownership survives the upgrade and every interactive session is then refused
with recording_unavailable.
Check the ownership before opening a session:
docker compose exec -T backend stat -c '%u:%g' /data/praxis/recordings# -> 1000:1000 is correct# -> 0:0 needs the repair belowIf it reports 0:0, repair the ownership in place:
docker compose exec -T --user root backend chown -R 1000:1000 /data/praxis/recordingsDo not delete and recreate the volume. It holds the recorded sessions for
every host your operators have connected to, and those cast files are audit
records. The chown above preserves them.
Confirm the repair as the backend user, which is who actually writes recordings
(docker compose exec runs as that user unless you override it):
docker compose exec -T backend sh -c \ 'touch /data/praxis/recordings/.write-check \ && rm /data/praxis/recordings/.write-check \ && echo writable'# -> writableNo restart is needed. The next session opens and records normally.
Rolling back
Re-pin PRAXIS_VERSION to the version you recorded before upgrading and
pull + up -d again. Note that database migrations are not automatically
reversed; if a migration ran, restore the pre-upgrade database backup before
starting the older image.
Known limitations in 1.0
- Single-instance backend only. Horizontal scale-out is not supported, and Docker Swarm is explicitly out of scope for 1.0.
- Single worker with interactive SSH. Interactive session runtime is
process-local, so multi-worker interactive SSH sessions are not supported;
keep
UVICORN_WORKERS=1. - Bring your own OIDC provider. Praxis does not bundle an identity provider.
- Manual release smokes. The prod-overlay, end-to-end, upgrade, and backup/restore smokes are manual release gates, not blocking CI lanes.
- Free edition host cap. The open-core free edition is capped at 15 managed hosts; larger fleets require a license.