impertio-studio/frappe-ops-bench
> Use when running bench commands, managing sites, configuring multi-tenancy, or setting up domains. Prevents misconfigured bench environments, broken site routing, and DNS mismatches. Covers bench CLI commands, site creation, bench init, multi-tenancy setup, DNS-based routing, common-site-config.
npx skills add https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-ops-bench
Complete bench CLI reference for site management, app lifecycle, configuration, and multi-tenancy.
Version: v14/v15/v16
| Task | Command |
|------|---------|
| Create bench | bench init myproject --frappe-branch version-15 |
| Create site | bench new-site mysite.localhost --admin-password admin |
| Set default site | bench use mysite.localhost |
| Get app | bench get-app erpnext --branch version-15 |
| Install app | bench --site mysite install-app erpnext |
| Start dev server | bench start |
| Run migrations | bench --site mysite migrate |
| Build assets | bench build --app myapp |
| Backup site | bench --site mysite backup |
| Restore backup | bench --site mysite restore /path/to/backup.sql.gz |
| Open console | bench --site mysite console |
| Open DB shell | bench --site mysite mariadb |
| Check scheduler | bench doctor |
| View pending jobs | bench show-pending-jobs |
| Update everything | bench update |
| Drop site | bench drop-site mysite --force |
# Initialize bench with specific Frappe version
bench init myproject --frappe-branch version-15
# With Python version
bench init myproject --frappe-branch version-15 --python python3.11
# Enter bench directory (REQUIRED for all subsequent commands)
cd myproject
What bench init creates:
myproject/
├── apps/ # Installed Frappe apps (frappe is default)
├── sites/ # All sites and shared config
│ └── common_site_config.json
├── config/ # Redis, Procfile, supervisor configs
├── env/ # Python virtual environment
├── logs/ # Log files
└── Procfile # Process definitions for bench start
--frappe-branch to pin Frappe version# Basic site creation
bench new-site mysite.localhost --admin-password admin
# With specific database
bench new-site mysite.localhost --db-name mysite_db --admin-password admin
# With MariaDB root password
bench new-site mysite.localhost --mariadb-root-password rootpass --admin-password admin
# Install apps during creation
bench new-site mysite.localhost --admin-password admin --install-app erpnext
bench use mysite.localhost
# OR set environment variable for current session:
export FRAPPE_SITE=mysite.localhost
bench drop-site mysite.localhost --force
# Deletes database and archives site directory
sites/mysite.localhost/
├── site_config.json # Site-specific config (db credentials)
├── private/ # Auth-required files, backups
├── public/ # Publicly accessible files
├── locks/ # Scheduler lock files
└── task-logs/ # Scheduler task logs
# Download app from GitHub
bench get-app erpnext --branch version-15
bench get-app https://github.com/org/custom-app.git --branch main
# Install app on a site
bench --site mysite install-app erpnext
# List installed apps
bench --site mysite list-apps
# Remove app from site (creates backup first)
bench --site mysite uninstall-app custom_app
# Remove app from bench entirely
bench remove-app custom_app
# Switch app branch
bench switch-to-branch version-15 erpnext frappe
# Exclude app from updates
bench exclude-app custom_app
# Re-include app in updates
bench include-app custom_app
get-app before install-app — get downloads, install activatesuninstall-app — it deletes app-related databench remove-app# Full update (pull + migrate + build + restart)
bench update
# Update specific app only
bench update --pull --app erpnext
# Skip build step
bench update --no-build
# Skip backup
bench update --no-backup
# Reset to upstream (DESTROYS local changes)
bench update --reset
bench update executes these steps in order:
git pull)bench build)bench migrate)bench update in a screen/tmux session — it takes time--reset in production without understanding it does git reset --hard# Migrate specific site
bench --site mysite migrate
# Migrate all sites
bench --site all migrate
# Check if safe to migrate (no pending jobs)
bench --site mysite ready-for-migration
What bench migrate does:
bench update (done automatically)# Build all apps
bench build
# Build specific app
bench build --app myapp
# Build with bundle analyzer
bench build --app myapp --production
# Watch mode (auto-rebuild on file changes)
bench watch
bench get-app (done automatically)package.json# IPython console (with Frappe loaded)
bench --site mysite console
# In console:
# >>> frappe.get_doc("Sales Invoice", "INV-001")
# >>> frappe.db.sql("SELECT name FROM `tabUser` LIMIT 5")
# Auto-reload on code changes
bench --site mysite console --autoreload
# MariaDB shell
bench --site mysite mariadb
# >>> SELECT name, email FROM tabUser LIMIT 5;
# PostgreSQL shell
bench --site mysite postgres
# Execute a method directly
bench --site mysite execute myapp.tasks.daily_cleanup
bench --site mysite execute myapp.api.process --kwargs '{"name": "INV-001"}'
# Make authenticated request as Administrator
bench --site mysite request GET /api/resource/User
# Backup (database + files)
bench --site mysite backup
# Creates: sites/mysite/private/backups/
# YYYY-MM-DD_HHMMSS-mysite-database.sql.gz
# YYYY-MM-DD_HHMMSS-mysite-files.tar
# YYYY-MM-DD_HHMMSS-mysite-private-files.tar
# Backup all sites
bench backup-all-sites
# Backup with encryption
bench --site mysite backup --backup-encryption-key mykey
# Restore from backup
bench --site mysite restore /path/to/database.sql.gz
# Restore with files
bench --site mysite restore /path/to/database.sql.gz \
--with-public-files /path/to/files.tar \
--with-private-files /path/to/private-files.tar
# Partial restore
bench --site mysite partial-restore /path/to/database.sql.gz
bench update, uninstall-app, or drop-sitekeep_backups_for_hours# Enable/disable scheduler
bench --site mysite scheduler enable
bench --site mysite scheduler disable
bench --site mysite scheduler pause
bench --site mysite scheduler resume
# Check scheduler health
bench doctor
# View queued jobs
bench show-pending-jobs
# Purge pending jobs
bench --site mysite purge-jobs
# Manually trigger scheduler event
bench --site mysite trigger-scheduler-event hourly
# Start worker manually (for debugging)
bench worker --queue short
# Enable DNS multi-tenancy
bench config dns_multitenant on
# Create sites with proper hostnames
bench new-site site1.example.com --admin-password admin
bench new-site site2.example.com --admin-password admin
# Regenerate nginx config
bench setup nginx
# Reload nginx
sudo service nginx reload
Requests are routed by matching the Host header to site names.
bench config dns_multitenant off
bench new-site site2.localhost --admin-password admin
bench set-nginx-port site2.localhost 8082
bench setup nginx
sudo service nginx reload
# Add domain to site
bench setup add-domain site1.example.com --site mysite
bench setup nginx
sudo service nginx reload
Located at sites/common_site_config.json — applies to ALL sites.
| Key | Default | Purpose |
|-----|---------|---------|
| background_workers | 1 | Number of background job workers |
| developer_mode | false | Auto-sync DocType changes to files |
| dns_multitenant | false | Enable DNS-based multi-tenancy |
| gunicorn_workers | 2 | Web server worker count (min: 2) |
| maintenance_mode | 0 | Take all sites offline |
| pause_scheduler | 0 | Pause job scheduler |
| serve_default_site | — | Default site when host not matched |
| server_script_enabled | false | Enable Server Scripts |
| scheduler_tick_interval | 60 | Seconds between scheduler checks |
| webserver_port | 8000 | Development server port |
| socketio_port | 9000 | Socket.IO port |
| live_reload | false | Auto-reload on asset rebuild |
| Key | Default |
|-----|---------|
| redis_cache | redis://localhost:13000 |
| redis_queue | redis://localhost:11000 |
| redis_socketio | redis://localhost:13000 |
# Set common config (all sites)
bench config set-common-config -c background_workers 4
bench config set-common-config -c developer_mode 1
# Set site-specific config
bench --site mysite set-config developer_mode 1
bench --site mysite set-config maintenance_mode 1
# View current config
bench --site mysite show-config
Per-site config at sites/<sitename>/site_config.json.
| Key | Purpose |
|-----|---------|
| db_type | mariadb or postgres |
| db_name | Database name |
| db_password | Database password |
| Key | Purpose |
|-----|---------|
| admin_password | Administrator initial password |
| host_name | Full site URL (with protocol) |
| install_apps | Apps to install on restore/reinstall |
| allow_cors | CORS origins ("*", URL, or array) |
| max_file_size | Upload limit (default: 10MB) |
| mute_emails | Disable all outgoing email |
| logging | Debug level (0-2, level 2 shows SQL queries) |
Environment variables override config files. Key mappings: FRAPPE_REDIS_QUEUE, FRAPPE_REDIS_CACHE, FRAPPE_DB_HOST, FRAPPE_DB_PORT, FRAPPE_DB_NAME, FRAPPE_DB_PASSWORD.
Priority: Environment Variable > site_config.json > common_site_config.json > Default
sudo bench setup production frappe-user # nginx + supervisor + fail2ban
bench setup lets-encrypt mysite.example.com # SSL
sudo bench restart # Restart services
bench disable-production # Back to development
| Feature | V14 | V15 | V16 |
|---------|:---:|:---:|:---:|
| bench init | Yes | Yes | Yes |
| Scheduler tick interval | ~240s | ~240s | 60s |
| db_user config (separate) | No | No | Yes |
| console --autoreload | No | Yes | Yes |
| trim-tables command | No | Yes | Yes |
| trim-database command | No | Yes | Yes |
| request command | No | Yes | Yes |
| Gettext translations | No | No | Yes |
| File | Contents |
|------|----------|
| commands.md | Full command reference with all options |
| examples.md | Common workflow examples |
| custom-commands.md | Creating custom bench CLI commands with Click |
| anti-patterns.md | Common bench mistakes and fixes |
Take impertio-studio/frappe-ops-bench from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.