"I need to bump from version X to Y without breaking SSO, losing data, or breaking the workflows that talk to it."
Same pattern for any service. Take a backup, change the image tag in the manifest, release-run, verify, rollback if needed.
PMA's service versions are pinned in each packages/<svc>/manifest.yaml:
image:
name: redmine
tag: '5-alpine' # ← this is the version you're upgrading
env_var: REDMINE_IMAGE_TAG
The release orchestrator notices the manifest change in phase 1, regenerates migrate.sh to force-recreate the service in phase 2, runs your per-ticket migration script in phase 3 if you wrote one, verifies in phase 4. Atomic, with a rollback path.
We'll walk a Redmine 5 → 6 upgrade as the worked example.
Before touching anything, read the release notes for the version you're moving to. Most upgrades are clean; some need explicit migration steps (schema changes, config format changes, deprecated features).
# Find the upstream changelog
curl -s https://www.redmine.org/projects/redmine/wiki/Changelog | head -100
Note any "breaking changes" or "manual steps required". These determine whether you need a per-ticket migration script (releases/<ticket>-redmine-upgrade.sh) or can rely on the framework's auto-migrate.
just ticket create "Upgrade Redmine 5 → 6" --tracker=feature --epic=3656
# Returns ticket ID, e.g. 4001
Use this ticket ID for the rest of the steps.
NEVER test the upgrade on prod directly. Clone into a fresh env:
ssh prod 'cd /opt/asd-pma && just bootstrap-local enterprise vs-redmine-upgrade'
# Bootstraps a fresh /opt/pma-vs-redmine-upgrade/ with all of enterprise
# Optionally restore the prod redmine DB into the test env so you upgrade
# against real data:
just backup redmine # on prod
scp prod:.asd/workspace/backups/redmine/<ts>/dump.sql /tmp/
ssh prod 'cd /opt/pma-vs-redmine-upgrade && cat /tmp/dump.sql | just redmine-psql'
Now you have a test env with realistic data.
git checkout -b feat/4001-redmine-upgrade
$EDITOR packages/redmine/manifest.yaml
# image.tag: '5-alpine' → image.tag: '6-alpine'
If the upgrade needs a per-ticket script (db migration, config rewrite), create one:
# releases/4001-redmine-upgrade.sh
#!/usr/bin/env bash
set -euo pipefail
# 1. Wait for redmine to be healthy after force-recreate
until just status redmine | grep -q healthy; do sleep 5; done
# 2. Run Redmine's built-in migration
docker exec ${CONTAINER_PREFIX}redmine rake db:migrate RAILS_ENV=production
# 3. Re-generate session token
docker exec ${CONTAINER_PREFIX}redmine rake generate_secret_token
echo "✓ #4001 Redmine 5 → 6 migration complete."
Mark executable + commit:
chmod +x releases/4001-redmine-upgrade.sh
git add packages/redmine/manifest.yaml releases/4001-redmine-upgrade.sh
git commit -m "feat(redmine): #4001 upgrade 5 → 6"
git push origin feat/4001-redmine-upgrade
# Open PR, get reviewed (don't merge yet)
# Get the feature branch onto the test env
ssh prod 'cd /opt/pma-vs-redmine-upgrade && git fetch origin feat/4001-redmine-upgrade && git checkout feat/4001-redmine-upgrade'
# Test release (use --dry-run first if you want)
ssh prod 'cd /opt/pma-vs-redmine-upgrade && just release-run 4001 --force-allow-non-main'
# (--force-allow-non-main since you're not on main yet)
If the release succeeds + your manual verification passes (Redmine works, tickets visible, SSO still works), merge the PR.
# Merge PR via GitHub UI
# Then on prod:
ssh prod 'cd /opt/asd-pma && just release-run 4001'
The release-run takes a fresh backup in phase 1, force-recreates Redmine in phase 2 (picks up the new image), runs the per-ticket script in phase 3, verifies in phase 4.
ssh prod 'cd /opt/asd-pma && just status redmine'
ssh prod 'curl -fsSI ${REDMINE_URL}'
# Open Redmine in browser → log in → check a few projects + tickets
ssh prod 'cd /opt/asd-pma && just env destroy vs-redmine-upgrade --force'
Service running new version. Backup from phase 1 available for rollback if needed:
ssh prod 'cd /opt/asd-pma && cat .asd/workspace/releases/4001.env'
# TICKET=4001
# BACKUPS="redmine=20260518_153012"
Rollback if anything's wrong:
ssh prod 'cd /opt/asd-pma && just release-revert 4001'
# Restores the backup taken in phase 1, reverts the merge commit,
# Redmine is back to version 5.
| Service | What to watch |
|---|---|
| Redmine | Major versions need rake db:migrate. Sometimes plugins need re-install. |
| Mattermost | Postgres schema migrations automatic. Sometimes plugins (calls, board) need re-config. |
| Authentik | Has its own migration system; usually clean. Major versions occasionally rotate signing keys → re-run just sso-fix-all. |
| ERPNext | Frappe runs bench migrate on startup. Major versions can take 30+ min on a large dataset. |
| n8n | Workflow format usually stable. Major versions sometimes deprecate node types — check the changelog. |
| Wiki.js | DB migration automatic. Theming/code injection (e.g. Mermaid 11 via configure-mermaid.ts) may need re-application — see the release-run script for #3901 as an example. |
| Postgres | NEVER bump major versions without a planned dump → restore. PMA pins postgres versions per service for this reason. |
/pma/learn/06-release-and-rollback.debug-a-failed-release.