2. Precautions Performed
2.1 Pre-Migration Assessment (Before Touching Anything)
Read all Frappe v14, v15, v16 GitHub changelogs and migration notes end to end
Ran FRAPPE_DEPRECATION_WARNINGS=1 on production v14 bench and captured all warnings to a log file
Used grep and ast-grep to scan every custom app Python file for deprecated API patterns in both v15 and v16
Used eslint with frappe-lint rules to scan all client-side JS for deprecated API calls
Inventoried all hooks.py entries across all custom apps against v15 and v16 hook structure changes
Confirmed Redis, MariaDB, Node, and Python versions on production server against v16 minimum requirements
Identified all third-party pip packages across all custom apps and checked Python 3.11 compatibility
Defined go/no-go criteria for each staging hop before production cutover was approved
2.2 Environment 2: V15 Staging Bench Setup
ENVIRONMENT 2 — V15 Staging Bench
Purpose: Validate V14 → V15 hop. Test custom apps on V15. Generate clean V15 DB backup for hop 2.
A completely fresh server was provisioned. No in-place upgrade of the v14 bench was attempted.
V15 Staging Server Specification
OS : Ubuntu 24.04 LTS (same as planned production upgrade target)
Python : 3.10.x via pyenv (v15 minimum; v16 will need 3.11 — covered in Env 3)
Node.js : 18.x via nvm
Redis : 6.x (v15 compatible; v16 upgrade handled in Env 3)
MariaDB : 10.11 LTS
bench CLI : Latest stable (v5.22+)
Frappe Branch: version-15
V15 Staging Setup Steps
bench init frappe-bench-v15 --frappe-branch version-15
bench get-app erpnext --branch version-15 (and all other required apps on v15 branches)
Install each custom app on its v15-compat Git branch: bench get-app <app> --branch v15-compat
bench new-site <site_name> --db-name <db> (create site shell)
Restore v14 production DB dump into MariaDB: mysql -u root -p <db> < v14_backup.sql
Convert DB charset: ALTER DATABASE <db> CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
bench --site <site> install-app <each_app> (register apps on the site)
bench --site <site> migrate (apply all v15 patches — monitor output carefully)
Resolve any migrate errors, re-run until fully clean
bench build --production (build v15 assets — resolve any Vite/Node errors)
Smoke test: login to desk, open key DocTypes, run a background job
2.3 V15 Custom App Remediation
Each custom app was given a v15-compat branch with the following changes applied and tested on the v15 staging bench before v16 work began.
Fixed hooks.py: renamed jinja_filters to jinja, updated app_include paths, fixed on_login to on_session_creation
Fixed frappe.db.sql() calls missing explicit values tuples
Fixed frappe.db.set_value() calls that relied on implicit auto-commit
Removed all usages of frappe.utils.make_esc()
Updated website_route_rules entries with required fields
Validated all custom reports, print formats, and notification templates on v15
Ran full pytest suite on v15 staging for each custom app
V15 UAT Sign-Off Gate
V15 staging bench must achieve UAT sign-off from key users before any V16 work begins.
V16 environment setup starts ONLY after a clean V15 DB backup has been verified.
The V15 DB backup (not the original V14 backup) is the input to the V16 staging bench.
2.4 Environment 3: V16 Staging Bench Setup
ENVIRONMENT 3 — V16 Staging Bench
Purpose: Validate V15 → V16 hop. Test custom apps on V16. Final UAT before production cutover.
A second fresh server was provisioned. The input database was the clean V15 backup, not the original V14 backup.
V16 Staging Server Specification
OS : Ubuntu 24.04 LTS
Python : 3.11.x via pyenv (v16 minimum requirement)
Node.js : 18.x via nvm
Redis : 7.x from official Redis apt repository (v16 requirement)
MariaDB : 10.11 LTS
bench CLI : Latest stable (v5.22+)
Frappe Branch: version-16
V16 Staging Setup Steps
bench init frappe-bench-v16 --frappe-branch version-16
bench get-app erpnext --branch version-16 (and all other apps on v16 branches)
Install each custom app on its v16-compat Git branch: bench get-app <app> --branch v16-compat
bench new-site <site_name> --db-name <db>
Restore the V15 staging DB backup (NOT the v14 backup) into MariaDB
bench --site <site> install-app <each_app>
bench --site <site> migrate (apply v16 patches only — far fewer than if coming from v14 directly)
Resolve migrate errors, re-run until clean
bench build --production (resolve Vite errors — more extensive than v15 due to Vue 3 changes)
bench setup supervisor --yes && sudo supervisorctl reread && sudo supervisorctl update
Full smoke test: all modules, background jobs, scheduler, print formats, emails, file uploads
2.5 V16 Custom App Remediation
Created v16-compat branch from v15-compat (not from main) for each custom app
Migrated all Vue 2 components to Vue 3 Composition API
Replaced all frappe.call() callback usages with Promise .then() chains
Added vite.config.js to each custom app; removed webpack.config.js
Updated hooks.py: corrected app_include paths for Vite dist output, doc_events priority, scheduler cron expressions
Fixed all deprecated Python API calls listed in Section 1.2.5
Replaced frappe.db.exists() calls that used return value as doc name
Fixed frappe.get_all() calls with or_filters as positional argument
Updated requirements.txt for all apps: resolved Python 3.11 package conflicts
Re-ran full pytest test suite on v16 staging for each custom app
2.6 Backup Strategy Across All Three Environments
Stage
Backup Taken
Used As Input For
V14 Production
Full mysqldump + bench/sites/ snapshot tagged v14-premig
V15 Staging Bench restore
V15 Staging (post-migrate)
mysqldump after clean V15 migrate, tagged v15-clean
V16 Staging Bench restore
V16 Staging (post-UAT)
mysqldump after V16 UAT sign-off, tagged v16-uat
Reference; production uses fresh V15 backup at cutover
V14 Production (cutover day)
Fresh mysqldump taken immediately before cutover begins
V16 Production Bench restore
Critical Backup Note
On production cutover day, a FRESH backup of V14 production is taken — not the staging backup.
This fresh backup is then migrated through the same validated V14→V15→V16 path on production servers.
The staging runs prove the migration process works; production uses fresh data through the same process.
2.7 Change Freeze & Communication
72-hour change freeze on all production apps before cutover (from Day 15 onwards)
No merges to any custom app main/v15-compat/v16-compat branches during freeze
All developers briefed on the two-step process and their role on cutover day
End users notified by Day 10 with estimated downtime window (typically 6 to 12 hours for two-hop production cutover)
Rollback trigger criteria defined: if production V15 migrate exceeds 14 hours or any P0 error occurs, rollback to V14 backup