# Upgrading an existing site > **Topology: Docker, or a bare-metal venv install.** An air-gapped Docker site > upgrades by loading a newer image tarball - > [DEPLOY-AIRGAP](DEPLOY-AIRGAP.md#upgrading-to-a-new-version), not this page. > **Where this sits:** install -> operate -> **upgrade**. > Back: [DEPLOY](DEPLOY.md), which is also the page for a first-time install. Each site is single-tenant (ADR-004), so an upgrade touches only that site's own stack. Read the ADRs added since your last update (`docs/adr/`) and the `CHANGELOG.md` before starting; a breaking ADR may require coordinated work. ## Step 0: Back up first Never upgrade without a fresh backup you have tested: a full database dump and a copy of `instance/`. [BACKUP-RESTORE](BACKUP-RESTORE.md) is the procedure. ```bash docker compose exec -T db mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" shopdb_flask | gzip > pre-upgrade-$(date +%F).sql.gz cp -a instance/ instance-backup-$(date +%F)/ ``` ## Step 1: Get the new code ```bash git pull ``` The application is distributed as source, and a production site builds its own image from that checkout. Sites do not pull a published image: CI publishes channel images to a container registry for dev and pilot workstations only (`docker-compose.pull.yml`, `.github/workflows/publish-images.yml`). ## Step 2: Rebuild The Docker image builds the Vue frontend in-image, so a container rebuild picks up frontend changes automatically: ```bash docker compose build api docker compose up -d api ``` Bare-metal / venv install: rebuild the frontend by hand and refresh Python dependencies: ```bash source venv/bin/activate pip install -r requirements.txt cd frontend && npm ci && npm run build && cd .. ``` `up -d api` REPLACES the container, so everything the site has written must be in the `instance_data` volume. If your stack predates that volume, do the one-time rescue in [DEPLOY](DEPLOY.md#the-instance_data-volume-and-stacks-that-predate-it) **before** this step, not after. ## Step 3: Migrate and re-seed **Under compose this is automatic.** `docker compose up -d api` will not start `api` until the `migrate` service has run `db upgrade`, the plugin bootstrap, `plugin upgrade-all` and the three seeders and exited 0. Watch it: ```bash docker compose logs migrate # the last line is the error, if there was one ``` A venv install has no `migrate` service and runs them itself: ```bash flask db upgrade flask plugin upgrade-all flask seed permissions flask seed settings flask seed reference-data ``` All of them are idempotent, so running them at head or on an already-seeded database is a no-op; the seeders add anything a new version introduced and leave your site's customized values alone. `plugin upgrade-all` is the one people skip - each plugin owns its own Alembic chain (ADR-008), and missing it surfaces days later as a 1054 "Unknown column" on the page that uses the new field. [DEPLOY](DEPLOY.md#step-3-the-schema-and-what-the-migrate-service-already-did) explains what each command covers. ## Step 4: Restart and confirm ```bash docker compose restart api # venv: restart your process manager, e.g. pm2 restart shopdb-flask-api shopdb-flask-ui docker compose exec api flask db current # must match `flask db heads` ``` Then log in: the page renders, and `/api/auth/login` returns a `VALIDATION_ERROR` for an empty body rather than a 500. ## Version-specific notes ### Upgrading to v0.11.0: the floor map became buildings and levels Nothing to do, and nothing moves on screen - the ordinary Step 3 migration handles it. `7d33_buildings_and_levels` creates one building ("Main") and one default level ("Ground floor") carrying the blueprint paths and dimensions from your `map_*` settings, then assigns every already-placed asset and location to that level. Unplaced things stay unplaced. The old settings rows are deliberately left in place, so a downgrade still finds the blueprint. Two things to know afterwards: - **A second floor or building is a Settings task**, not a migration: add it under Settings > Buildings and levels, upload its blueprint, then move the markers that belong on it. `POST /api/mappositions/transform` moves a whole level's markers onto a redrawn plan from landmark pairs, dry run by default. - **Replacing a blueprint with one of DIFFERENT dimensions moves every marker on that level**, because the coordinates are pixels of the old size. Upload it, then recalibrate with landmarks rather than editing the level's width and height by hand. The API refuses to adopt a new size on a level that already has markers, for this reason. If you wrote a plugin that stores map positions, see CONTRACT-STABILITY.md: the contract is 0.20.0 and a position must now carry its `levelid`. ### Upgrading from before v0.5.0: the bundled floor plan was removed Only affects an instance whose `map_blueprint_light` / `map_blueprint_dark` Settings still point at `/static/images/sitemap2025-*`. Those bundled PNGs are gone from v0.5 on, so the map 404s them after the upgrade. Re-upload your own plan in **Settings > Floor Map**. Uploaded plans live under `instance/` and survive upgrades, so a site that already uploaded one is unaffected. ```bash docker compose exec -T api flask shell <<'PY' from shopdb.core.models.setting import Setting print(Setting.query.filter(Setting.key.like('map_blueprint%')).all()) PY ``` ## See also - [BACKUP-RESTORE](BACKUP-RESTORE.md) - what to back up and how to restore - [CONFIG](CONFIG.md) - environment variables and Setting keys - [DEPLOY](DEPLOY.md) - first-time deploy runbook - `CHANGELOG.md` - what changed in each release