02.02 Upgrading via Docker
Pull the new image tag, stop the old container, start the new container with the same volume mounts. The application code comes from the new image; the database and configuration persist via the volumes; the schema migrations run on the next page load.
Why this matters
The Docker upgrade workflow is materially different from the bare-metal workflow even though the underlying SimpleRisk upgrade architecture is identical. On bare metal you replace files on disk and run the database upgrade as separate steps; on Docker you swap containers and the new container's startup runs the schema upgrade automatically. The difference is in the deployment shape, not in what's happening to SimpleRisk's data.
The honest scope to know: this article assumes you're running SimpleRisk via the official Docker images from the simplerisk/docker repository (per Installing via Docker), with persistent volume mounts for config.php, the uploads directory, and (if using embedded MySQL) the database. The upgrade workflow described here assumes that volume-mount setup; deployments with non-standard mount patterns may need adjustment.
The other thing worth knowing: the persistence pattern matters more than the upgrade procedure. If config.php isn't mounted to a persistent volume, replacing the container loses the install's database credentials and the upgrade has nothing to authenticate against. If the database is in a volume that the new container can't read, the new application code can't reach the data. Verify the volume-mount setup before the upgrade — this is one of those cases where a problem surfaced during the upgrade is much harder to fix than the same problem caught beforehand.
Before you start
Have these in hand:
- The current SimpleRisk version — check with
docker execor via thephp -r 'require "/var/www/simplerisk/includes/version.php"; echo APP_VERSION;' GET /api/upgrade/version/appendpoint. - The target SimpleRisk version — typically the latest release tag from the
simplerisk/dockerimage registry. Pin to a specific version tag, notlatest. - The release notes for every version between current and target. Each release's notes (under
release_notes/in the SimpleRisk repository) document changes including upgrade-specific notes. For multi-version upgrades, read the notes for each version in the chain. - A current database backup. See Database Backup and Restore. Skipping this is the single most common upgrade regret.
- A maintenance window scheduled if your install has active users. The upgrade typically completes in minutes for routine upgrades and minutes-to-hours for upgrades that span many releases.
- Confirmation that your volumes persist
config.php, uploads, and the database. Rundocker volume ls(ordocker inspect) to verify the mount points.
Step-by-step
1. Take a database backup
This is the rollback path; without it, a failed upgrade is recoverable only by running the upgrade forward through whatever issue blocked it.
For deployments using a separate MySQL container with the standard pattern:
docker exec
mysqldump -u root -p
simplerisk > simplerisk-backup-$(date +%Y%m%d-%H%M%S).sql
For deployments using embedded MySQL inside the SimpleRisk container, exec into the container first.
Verify the backup is non-empty and looks reasonable (tail simplerisk-backup-*.sql should show table-definition or insert statements, not error messages or zero bytes).
For deployments using a managed cloud database, use the cloud provider's backup mechanism (RDS automated snapshot, Azure DB on-demand backup, Cloud SQL backup) and verify the backup completed before proceeding.
2. Take a config.php backup
The config.php file holds database credentials and install-specific values. Lose it during the upgrade and you're rebuilding from scratch.
docker cp
:/var/www/simplerisk/includes/config.php ./config.php.backup
If config.php is on a host bind mount, also copy it from the host filesystem; if it's on a Docker named volume, the docker cp is the easiest path.
3. Pull the new image
docker pull simplerisk/simplerisk:
Verify the image pulled successfully:
docker images simplerisk/simplerisk
For air-gapped deployments where the SimpleRisk host can't reach Docker Hub, use the standard Docker image-transfer pattern: docker save simplerisk/simplerisk:
on a connected machine, transfer the tarball to the SimpleRisk host, docker load < simplerisk-image.tar on the host.
4. Update the Compose file (if applicable)
If you're running with Docker Compose, edit the Compose file to reference the new image tag:
services:
simplerisk:
image: simplerisk/simplerisk:
# changed from old version
Save the file. Don't run docker compose up yet — the next step does the orchestrated upgrade.
5. Stop the current container and start the new one
For Docker Compose deployments:
docker compose down
docker compose up -d
docker compose down stops and removes the existing SimpleRisk container (and the MySQL container if it's in the same Compose file — that's fine, it'll come back with the same data via the volume mounts). docker compose up -d starts the new SimpleRisk container from the updated image, with the same volume mounts.
For non-Compose deployments, the equivalent commands are:
docker stop
docker rm
docker run -d \ --name
\ -v
:/var/www/simplerisk/includes/config.php \ -v
:/var/www/simplerisk/uploads \ -v
:/var/www/simplerisk/logs \ --link
:mysql \ -p 80:80 \ simplerisk/simplerisk:
The new container starts with the new application code; the persistent volumes carry forward the data and configuration.
6. Wait for the container to be healthy
docker compose ps
# OR:
docker ps
The container should show as Up and (if a healthcheck is defined) (healthy). If it shows as restarting or unhealthy, check the logs:
docker compose logs simplerisk
# OR:
docker logs
Common startup failures: the container can't reach MySQL (network issue, MySQL container not yet ready), the volume mount for config.php is missing or empty (the new container doesn't know how to connect to the database), the new image's PHP version isn't compatible with the existing data (rare; only happens on substantial PHP-version jumps).
7. Run the database upgrade
The application code is now the new version. The database schema is still the old version. Run the upgrade to bring the database up to match.
Browser-based:
- Open
https://in a browser. Log in with admin credentials./admin/upgrade.php - The upgrade page detects that
db_versionlagsAPP_VERSIONand runs the chain ofupgrade_from_*functions documented in The Upgrade Process. - Wait for the page to report success.
API-based (for scripted upgrades):
curl -H "X-API-KEY:
" \ https://
/api/upgrade/upgrade/simplerisk/db
The endpoint returns JSON status; on success, the database is now at the new version.
8. Upgrade the activated Extras
The Core upgrade doesn't bring Extras forward. After Core is at the new version, walk the activated Extras and run each one's upgrade. The full workflow is in Upgrading Extras; briefly, the API endpoints are GET /api/
for each Extra.
9. Verify the upgrade end-to-end
- Confirm
APP_VERSIONanddb_versionmatch:GET /api/upgrade/version/appshould equal the value ofdb_versionin the settings table. - Open the dashboard and confirm it loads.
- Submit a test risk and confirm it saves correctly.
- Trigger a test notification and confirm it sends.
- Spot-check the logs at
simplerisk/logs/for any new errors that didn't exist before the upgrade.
If anything looks broken, the rollback path is to swap back to the old image and restore the database from the Step 1 backup (see Database Backup and Restore for the restore mechanics).
10. Update operational runbooks
After the upgrade succeeds, update any operational runbooks that reference the SimpleRisk version. The image tag pinned in Compose files, the version mentioned in deployment documentation, the version in monitoring/alerting configurations — all of these should reflect the new version so the next operator (or your future self) doesn't get confused.
Common pitfalls
A handful of patterns recur with Docker upgrades.
-
Pulling without pinning. A
docker pull simplerisk/simplerisk:lateston production picks up whatever's currently latest, which may not be the version you intended. Always pin to a specific tag for production upgrades; the pinned tag is the version you tested in staging. -
Stopping the container before backing up the database.
docker compose downstops the MySQL container too (in single-Compose-file deployments). If you stopped MySQL before running mysqldump, the dump fails. Take the backup first, then stop. -
Forgetting the config.php volume. A common deployment mistake is not mounting
config.phpto a persistent volume — it lives in the container's writable layer, which is destroyed when the container is replaced. The new container has noconfig.phpand can't reach MySQL. Verify the volume mount before the first upgrade; ideally fix this on the install rather than discovering it during an upgrade. -
Skipping the database upgrade after the image swap. The new container starts and the application loads, but the schema is at the old version. Some pages start erroring as code paths reference schema elements that don't exist yet. Run the database upgrade promptly after the image swap.
-
Running the database upgrade against a database that's still being modified. If users are still hitting the application during the upgrade, the schema changes can conflict with concurrent transactions. Take the application offline (point the load balancer's health check to a maintenance page, scale the SimpleRisk container to 0 replicas in Kubernetes, etc.) for the duration of the upgrade, or schedule the upgrade during a maintenance window.
-
Not testing the rollback path. "We can always roll back" is true only if the rollback works. Test the rollback (restore from backup, swap to old image) in a non-production environment before running the upgrade in production. The first time you exercise the rollback path shouldn't be during a production incident.
-
Ignoring the upgrade logs. The upgrade UI and API both produce logs that show the chain of
upgrade_from_*functions executing. Log review is the first diagnostic step when something goes wrong post-upgrade. -
Accumulating image tags on the host. Each pulled image takes disk space; a host that's pulled five releases over the past year has five SimpleRisk images. Periodic
docker image prunekeeps disk usage in check; for safety, prune after successful upgrades so the previous image is available for rollback for at least a few days. -
Upgrading the SimpleRisk image without updating the MySQL image. The MySQL container has its own version that should be kept current for security patches. The two images upgrade on independent cadences; make sure both are current per your patch policy.
Related
- The Upgrade Process
- Upgrading on Bare Metal
- Upgrading Extras
- Database Backup and Restore
- Installing via Docker
- The Cron Jobs
Reference
- Permission required:
check_adminfor the browser-based upgrade UI; valid API key for the v2 API. Server-side: Docker access, MySQL admin (for backup). - API endpoint(s):
GET /api/upgrade/version/app(currentAPP_VERSION);GET /api/upgrade/backup/db(database backup orchestration);GET /api/upgrade/upgrade/simplerisk/db(run the database upgrade). - Implementing files: The Docker images from the separate
simplerisk/dockerrepository; SimpleRisk'ssimplerisk/admin/upgrade.php(browser UI);simplerisk/includes/upgrade.php(the upgrade chain);simplerisk/includes/version.php(theAPP_VERSIONconstant). - Database tables:
settings(thedb_versionkey updated atomically byupdate_database_version()); the schema of every other table is what the upgrade functions modify. config_settingskeys:db_version(the database version stamp).- Volume mounts:
config.php(must persist), uploads directory (data continuity), logs directory (operational continuity), MySQL data directory (data persistence for embedded-MySQL deployments). - External dependencies: Docker Hub (or a private mirror) for image pulls; outbound HTTPS to the new image registry.