Skip to content

Upgrades — bumping submodule pointers

Polaris Express is composed as a superproject of git submodules — web, email-worker, ios, steve, and docs. Upgrading a self-hosted deployment means advancing those submodule pointers to a newer commit on their respective main branches, rebuilding the affected images, and restarting the stack.

This runbook covers the manual upgrade path. If you mirror our automation, you can also drive it from the Bump submodules GitHub Action shipped with the superproject.

  • Routine cadence. Weekly or biweekly. The reference automation runs every Monday at 09:00 UTC.
  • Security fixes. When an advisory affects any component.
  • Feature pulls. When you want a specific change from upstream.
  1. Take a backup.

    Snapshot the Postgres volume used by web and the MariaDB volume used by steve before doing anything else. If a migration fails, this is your only easy way back.

  2. Update the working tree.

    From the superproject root:

    Terminal window
    git fetch origin
    git checkout main
    git pull --ff-only
  3. Pull the latest commit on each submodule’s tracking branch.

    The update target wraps the two commands you want:

    Terminal window
    make update

    Which expands to:

    Terminal window
    git submodule update --remote --merge
    git diff --submodule=log

    The --submodule=log diff shows you the per-submodule commit log between the old and new pointers. Read it. This is your changelog.

  4. Review the diff.

    Look specifically for:

    • Migration files under web/drizzle/ (Postgres schema for the web app).
    • Migration files under steve/src/main/resources/db/migration/ (MariaDB schema for SteVe).
    • Env-var additions in web/.env.example. New required vars must be added to your deployment’s env file before you restart.
    • Compose changes in any submodule’s docker-compose.yml.
  5. Commit the pointer bumps.

    Terminal window
    git add web email-worker steve docs ios
    git commit -m "chore: bump submodules $(date -u +%Y-%m-%d)"
  6. Rebuild and restart.

    Terminal window
    make compose-build
    make down
    make up

    On first start after the bump, the web container will run any pending Drizzle migrations against Postgres, and SteVe will run any pending Flyway migrations against MariaDB.

  7. Tail the logs and watch for errors.

    Terminal window
    docker compose logs -f web steve

    Wait until both services log a steady-state ready message before moving on. Migration failures appear here and will keep the affected container in a restart loop.

If your superproject lives on GitHub, the shipped .github/workflows/bump-submodules.yml workflow does steps 1–5 automatically. It runs weekly (Monday 09:00 UTC) and on manual dispatch.

  • It checks out the superproject with submodules.
  • It runs git checkout main && git pull origin main inside every submodule.
  • If anything changed, it opens a PR titled chore: bump submodules on a branch named bump-submodules/<run-id>.

You still review the PR (especially the per-submodule diffs) and merge manually. Rebuild and restart are still your job — the workflow does not touch your deployment.

To run it on demand: Actions → Bump submodules → Run workflow.

After restart, confirm each surface independently.

Terminal window
curl -fsS https://<your-web-host>/api/health

Should return 200 OK. Then log into the admin console and check that the build SHA in the footer matches the commit you just bumped to.

The whole upgrade is one commit on the superproject. To roll back:

Terminal window
git revert <bump-commit-sha>
git submodule update --init --recursive
make compose-build
make down
make up

For destructive rollbacks:

  1. Stop the stack: make down.
  2. Restore the Postgres and MariaDB volumes from the pre-upgrade snapshot.
  3. git revert the bump commit and git submodule update --init --recursive.
  4. make compose-build && make up.
  5. Confirm health endpoints as above.
  • A submodule didn’t advance. make update uses --merge, which will fail noisily if your submodule working tree has local commits or uncommitted changes. cd into the submodule, clean it (git status, then git reset --hard origin/main if you have no local work), and re-run.
  • web container restart loop after upgrade. Almost always a Drizzle migration error. Check docker compose logs web for the failing SQL. If the migration is recoverable, fix the data and let it retry; if not, restore from backup.
  • steve container restart loop. Same pattern with Flyway against MariaDB. Logs will name the failing migration file.
  • New required env var. web will exit on boot with a clear message naming the missing variable. Add it to your env file and make up again. See the per-phase pages for which file owns which variable.
  • Backups — take these before every upgrade.
  • Monitoring — set this up so you find out about a failed migration before your users do.