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