Development Lifecycle & Devops
Frappe Development Lifecycle
Frappe follows a structured development lifecycle that separates concerns across environments — from local development to staging to production — using the bench CLI as the primary operational tool.
1. Environments
Each environment is an independent bench with its own sites, databases, and app versions. No code is deployed directly to production without passing through staging.
2. How Check-ins Are Done
All Frappe custom app code lives in a git repository. The check-in process follows standard git branching with Frappe-specific considerations:
Branching Strategy
What Gets Committed
- Python controllers — doctype_name.py with business logic
- JSON DocType definitions — schema, fields, permissions (doctype_name.json)
- JavaScript client scripts — doctype_name.js for form-side logic
- Fixtures — exported Custom Fields, Workflows, Print Formats as JSON
- Patches — one-time data migration scripts in patches.txt
- hooks.py — app-level configuration and overrides
Check-in Workflow
# 1. Create feature branch
git checkout -b feature/record-referral-enhancements
# 2. Make changes to DocType/Python/JS
# 3. Export any DocType changes to JSON (keeps schema in git)
bench export-fixtures --app custom_app
# 4. Stage and commit
git add .
git commit -m "feat: add referral amount calculation to CustomDocType"
# 5. Push and open Pull Request to develop
git push origin feature/record-referral-enhancements
Code Review checklist before merge:
- No direct changes to erpnext or frappe app code — only custom app
- DocType JSON re-exported after any field/permission changes
- Patches written for any data migrations
- No hardcoded site names or environment-specific values
- Background jobs use frappe.enqueue() — not blocking calls
3. How Rollout to Production is Done
Production deployments in Frappe use the bench update command combined with a pre-defined release process. No hot-patching of live files.
Standard Rollout Steps
# Step 1: Pull latest code for all apps on the bench
bench update --pull
# Step 2: Run database migrations (applies new columns, patches)
bench update --patch
# Step 3: Build JS/CSS assets
bench build --app custom_app
# Step 4: Restart services (Gunicorn workers + RQ workers)
bench restart
# Combined (most common for minor updates):
bench update --reset
Rollout Process with Maintenance Window
Zero-Downtime Deployments (Advanced)
- Use Kubernetes rolling updates — new pods with updated image replace old ones gradually
- Keep DB migrations backward-compatible — new columns are nullable, old code still works
- Use feature flags in frappe.conf to enable new features post-deployment
- Blue-green deployment: spin up new bench, switch Nginx upstream, keep old bench as fallback
Hotfix Process
# Branch off main (not develop) for critical fixes
git checkout -b hotfix/fix-fee-calculation main
# ... make fix ...
git commit -m "fix: correct fee calculation for partial payments"
# Merge to both main AND develop
git checkout main && git merge hotfix/fix-fee-calculation
git checkout develop && git merge hotfix/fix-fee-calculation
# Deploy to production immediately
bench update --patch && bench restart
4. Full Development Lifecycle
Environment Promotion Flow
feature/* ──► develop ──► staging ──► main ──► production

UAT sign-off Release tag
Key bench CLI Commands Reference
Frappe Development Lifecycle
Frappe follows a structured development lifecycle — from local development to production — with clear stages, tooling, and conventions enforced by the bench CLI.
1. Environments
Each environment runs a separate bench with its own MariaDB database, Redis instance, and site config. No shared state between environments.
2. Local Development Setup
# Install bench
pip install frappe-bench
# Initialise a new bench
bench init frappe-bench --frappe-branch version-16
cd frappe-bench
# Add ERPNext and custom app
bench get-app erpnext --branch version-16
bench get-app custom_app https://github.com/hybrowlabs/custom_app
# Create a new site
bench new-site yoursite.local --install-app erpnext --install-app custom_app
# Start development server
bench start
3. Code Check-in Process
All Frappe customisations live in the custom app repository (e.g., custom_app). The check-in workflow follows standard git practices:
# Create a feature branch
git checkout -b feature/record-referral-module
# Make changes — DocType JSON, Python controller, hooks.py
# Export DocType changes as fixtures
bench --site yoursite.local export-fixtures
# Stage and commit
git add .
git commit -m "feat: add Custom DocType doctype with lifecycle hooks"
# Push and raise Pull Request
git push origin feature/record-referral-module
4. Staging Deployment
After PR review and merge to the develop or staging branch, deploy to the staging environment:
# On staging server — pull latest code
cd /home/frappe/frappe-bench
bench update --pull --patch --build
# Or for the custom app only (faster)
bench update --apps custom_app
# Run migrations if schema changed
bench --site staging.yoursite.erpnext.com migrate
# Clear cache
bench --site staging.yoursite.erpnext.com clear-cache
Staging is used for client UAT (User Acceptance Testing). stakeholders validate functionality before production sign-off.
5. Production Rollout
Production deployments follow a controlled process to minimise downtime and risk:
# Step 1: Enable maintenance mode (shows maintenance page to users)
bench --site yoursite.erpnext.com set-maintenance-mode on
# Step 2: Pull latest code from release branch
bench update --pull --patch --build --restart-supervisor
# Step 3: Run database migrations
bench --site yoursite.erpnext.com migrate
# Step 4: Clear all caches
bench --site yoursite.erpnext.com clear-cache
bench --site yoursite.erpnext.com clear-website-cache
# Step 5: Disable maintenance mode
bench --site yoursite.erpnext.com set-maintenance-mode off
# Step 6: Restart services
sudo supervisorctl restart all
Typical downtime: 2–5 minutes for minor releases. Zero-downtime blue-green deployments are possible on Kubernetes with rolling updates.
6. Branching Strategy
7. Patch Management
One-time data migrations are handled via patches — Python scripts that run exactly once per site during bench migrate:
# patches/v1_0/backfill_custom_doctype_status.py
import frappe
def execute():
frappe.db.sql("""
UPDATE `tabCustom DocType`
SET status = "Pending"
WHERE status IS NULL OR status = ""
""")
frappe.db.commit()
Patches are registered in patches.txt and executed automatically during bench migrate — never run twice on the same site.
8. CI/CD Pipeline (Recommended)
For , a CI/CD pipeline via GitHub Actions or GitLab CI is recommended: