frappe-cli
███████╗██████╗ █████╗ ██████╗ ██████╗ ███████╗ ██████╗██╗ ██╗
██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔══██╗██╔════╝ ██╔════╝██║ ██║
█████╗ ██████╔╝███████║██████╔╝██████╔╝█████╗ ██║ ██║ ██║
██╔══╝ ██╔══██╗██╔══██║██╔═══╝ ██╔═══╝ ██╔══╝ ██║ ██║ ██║
██║ ██║ ██║██║ ██║██║ ██║ ███████╗ ╚██████╗███████╗██║
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚══════╝ ╚═════╝╚══════╝╚═╝
Frappe CLI · fp · v1.0.4
Install · Operate · Develop — all in one command
A production-ready CLI that installs and operates Frappe / ERPNext — from a bare VPS to a working HTTPS site in one command. Also your daily bench shortcut.
Built with Python + Click + Rich. Every step is independently runnable, self-healing, and idempotent — safe to re-run on partially-installed servers.
Two tools in one
┌─────────────────────────────────────────────────────────────────────┐
│ fp (frappe-cli) │
├──────────────────────────────┬──────────────────────────────────────┤
│ 🏗 INSTALLER / OPS │ ⚡ DAILY DEV WORKFLOW │
│ │ │
│ fp install wizard │ fp use <site> ← set context │
│ fp step <name> │ fp migrate ← auto --site │
│ fp ssl setup/list │ fp console │
│ fp service status │ fp restart │
│ fp backup setup │ fp build / watch │
│ fp firewall setup │ fp get-app <url> │
│ │ fp sites / fp context │
└──────────────────────────────┴──────────────────────────────────────┘
Highlights
- One-command bootstrap:
fp install wizardprovisions a fresh Ubuntu VPS end-to-end (MariaDB → Redis → Node → bench → site → ERPNext → nginx → supervisor → SSL). - 17 individually runnable steps:
fp step <name>lets you re-run, debug, or compose any wizard step on its own (same code path as the wizard). - Context-aware dev workflow:
fp use mysite.localremembers your active bench + site — then everybenchcommand becomes one word. - Multi-bench friendly: works on fresh and non-fresh VPS — auto-detects existing bench installs and directories.
- Self-healing: automatically repairs the supervisor symlink that vanilla
bench setup productionsometimes misses on multi-bench hosts. - Hard verification: polls
supervisorctl statusfor RUNNING and pings bench Redis for PONG before declaring success. - Resumable: the wizard saves progress; if a step fails you can fix and
fp install wizard --resume. - Operator-friendly utilities:
fp ssl list/setup,fp service status,fp backup setup,fp firewall setup, and more.
Installation
frappe-cli is published on PyPI: https://pypi.org/project/frappe-cli/.
After install, use
fpas your command everywhere (short for Frappe Platform; the per-bench context file is.fp.yaml).
With uv (recommended)
# Install uv (if you don't have it yet)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install frappe-cli globally
uv tool install frappe-cli
# Verify
fp --version
fp --help
To upgrade later:
uv tool upgrade frappe-cli
With pip
pip install --user frappe-cli
export PATH="$HOME/.local/bin:$PATH"
fp --help
With pipx
pipx install frappe-cli
fp --help
Requirements
| Requirement | Version |
|---|---|
| Python | ≥ 3.10 |
| OS | Ubuntu 22.04 or 24.04 (other Debian variants work, others untested) |
| User | A non-root user with sudo access |
Note:
frappe-cliwill install Node.js, MariaDB, Redis, nginx, supervisor, wkhtmltopdf, certbot, anduvfor you. You do not need to pre-install them.
Quick start: bootstrap a fresh VPS
The fastest path from a clean Ubuntu server to a working HTTPS Frappe site:
Fresh Ubuntu VPS
│
▼
apt install python3-pip curl
│
▼
uv tool install frappe-cli
│
▼
fp install wizard ← interactive prompts, ~30-60 min
│
├─ system-update
├─ system-deps
├─ nodejs
├─ mariadb-install + mariadb-secure
├─ redis
├─ wkhtmltopdf
├─ bench-install + bench-init
├─ site-create
├─ get-app + install-app
├─ production (nginx + supervisor)
└─ ssl (Let's Encrypt)
│
▼
✓ https://erp.example.com ← live in one command
sudo apt update && sudo apt install -y python3-pip curl
# 1. Install frappe-cli
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install frappe-cli
# 2. Run the interactive wizard (≈30–60 min depending on the host)
fp install wizard
You'll be prompted for:
| Prompt | Example |
|---|---|
| Bench directory | my-bench |
| Site (FQDN, must resolve to this host) | erp.example.com |
| Frappe branch | version-15 |
| App GitHub URL | https://github.com/myorg/vsd_fleet_ms (or blank) |
| App branch | auto-detected — see note below |
| MariaDB root password | (entered twice for confirmation) |
| Site Administrator password | (new password) |
| Let's Encrypt email | you@example.com |
| Sudo password | (your login password) |
Allow passwordless fp restart? |
Y recommended |
App branch auto-detection
The wizard detects the right branch automatically:
| App type | Default branch | How it works |
|---|---|---|
Official Frappe app (erpnext, hrms, payments, …) |
Same as Frappe branch (version-15) |
No network call — detected by name/URL |
| Custom / third-party app | Detected from remote heads | git ls-remote checks for version-15 → main → develop in priority order |
| Custom / private repo (auth fails) | main |
Falls back safely; shows a hint to set up SSH keys or Git credentials |
For private repos, use the SSH URL format before running the wizard:
# Make sure your SSH key is added to GitHub first
git@github.com:myorg/vsd_fleet_ms.git ← use this style in the URL prompt
MariaDB root password
The wizard asks for this password twice. Unlike the site admin password (which can be reset with bench set-admin-password), the MariaDB root password is harder to recover if entered incorrectly. Confirm carefully.
If a step fails, fix the issue and resume from where it stopped:
fp install wizard --resume
When it's done:
curl -I https://erp.example.com # HTTP/2 200, served by nginx, valid Let's Encrypt cert
⚡ Daily dev workflow
This is where fp really shines. Instead of typing bench --site <long.site.name> migrate every time, you set your context once and then use short commands.
~/my-bench/ ← bench root
├── apps/
│ ├── frappe/
│ └── my_custom_app/ ← you are here, deep in code
├── sites/
│ ├── dev.local/
│ └── staging.example.com/
└── .fp.yaml ← fp writes: site: dev.local
Step 1 — set your active site (once per session)
Works from any directory inside the bench — bench root, apps/, apps/my_app/, etc.
cd ~/my-bench
fp use dev.local
# ✓ Active site set to dev.local (bench: my-bench)
Step 2 — run bench commands without the boilerplate
# Before fp:
bench --site dev.local migrate
bench --site dev.local console
bench --site dev.local clear-cache
# With fp:
fp migrate
fp console
fp clear-cache
Switching between sites
You never "lose" a site — just use a different one. The old site is still there.
fp sites
● dev.local ← active (green dot)
staging.example.com
fp use staging.example.com
# ✓ Active site set to staging.example.com
fp migrate → bench --site staging.example.com migrate
One-command deploy
The most common prod/demo workflow — pull, migrate, then restart:
cd ~/my-bench/apps/my_custom_app
fp deploy
# → git pull
# → bench --site dev.local migrate
# → bench restart
# ✓ Deploy complete
Migrate runs before restart so schema and code changes apply cleanly.
Skip git pull when you only need migrate + restart:
fp deploy --no-pull
Make fp restart passwordless
On production benches bench restart (and therefore fp restart / fp deploy) uses sudo supervisorctl — which prompts for a password.
Enable passwordless restart in two ways:
Option A — during fp install wizard (recommended for new servers):
The wizard asks you at setup time:
Allow passwordless 'fp restart' for this user? [Y/n]
Answer Y and the wizard writes a safe sudoers drop-in automatically.
Option B — at any time with fp sudo:
fp sudo status # check current state
fp sudo enable-restart # grant passwordless supervisorctl (asks sudo once)
fp sudo disable-restart # revoke the rule
After enabling, fp deploy runs completely without password prompts:
→ git pull
→ migrate dev.local
→ restart bench
✓ Deploy complete
The sudoers rule is scoped to one user + one binary (/usr/bin/supervisorctl) — minimal privilege. The file is tagged so fp sudo disable-restart never removes a hand-crafted rule.
All dev commands
fp use <site> Write active site to .fp.yaml
fp context Show current bench + active site
fp sites List all sites (active site marked with ●)
fp deploy git pull → migrate → restart
── site-scoped (auto-injects --site) ─────────────────────────────
fp migrate Sync schema, run patches, rebuild assets
fp console IPython console for the active site
fp install-app <app> Install app on active site
fp uninstall-app <a> Remove app from active site
fp list-apps Apps installed on active site
fp clear-cache Clear framework cache
fp mariadb MariaDB shell for active site
── bench-scoped (no --site needed) ───────────────────────────────
fp restart Restart supervisor / systemd processes
fp build Build JS + CSS assets
fp start Start dev server (Procfile)
fp watch Watch + recompile JS/CSS on change
fp get-app <url> Download app from git URL
── sudoers management ────────────────────────────────────────────
fp sudo status Show passwordless restart state
fp sudo enable-restart Grant passwordless supervisorctl
fp sudo disable-restart Revoke the rule
How it works:
fpdetects the bench root by walking up from your current directory looking for both asites/andapps/folder. The active site is stored in<bench_root>/.fp.yaml— a plain YAML file you can inspect or edit directly. The wizard's state (~/.frappe-cli-state.json) is completely separate and untouched.
Command structure
fp <group> <command> [options]
| Group | What it does |
|---|---|
install wizard |
End-to-end automated installer with state + resume |
step |
Run any individual wizard step on its own |
ssl |
Issue / list Let's Encrypt certificates for existing sites |
site |
Create, list, backup, restore sites |
app |
Get / install / update / remove Frappe apps |
service |
Restart, status, logs for bench + system services |
backup |
Manual + scheduled backups (optionally to an external HD) |
firewall |
UFW configuration with secure defaults |
maintenance |
Log rotation, etc. |
monitor |
Live logs and system health |
optimize |
Performance tuning |
rollback |
Restore from backup, uninstall site, etc. |
config |
YAML config get/set/validate |
use |
Set active bench + site context |
context |
Show current bench + active site |
sites |
List sites in the current bench |
Every command supports --help and many support --dry-run and --debug.
Everyday recipes
Add SSL to an existing site
# See all sites and which are still on HTTP
fp ssl list
# Issue an HTTPS cert for one site (auto-detects the owning bench)
fp ssl setup --site-name erp.example.com
# First time ever using Let's Encrypt on this host? Provide an email
fp ssl setup --site-name new.example.com --email you@example.com
Re-run a single wizard step
Every step the wizard runs is also exposed as a standalone command. Same code, same self-healing, same verification.
# Show all steps in execution order
fp step list
# Run just SSL for one site
fp step ssl --bench-name my-bench --site-name erp.example.com
# Re-do the production setup (nginx + supervisor) for a bench
fp step production --bench-name my-bench
# Try a step without executing anything
fp step production --bench-name my-bench --dry-run
# Force a step even when check() says "already done"
fp step ssl --bench-name my-bench --site-name erp.example.com --force
The full step catalogue:
| # | Command | Purpose |
|---|---|---|
| 1 | fp step system-update |
apt-get update && upgrade |
| 2 | fp step system-deps |
Frappe's required apt packages |
| 3 | fp step uv-check |
Ensure uv is installed |
| 4 | fp step nodejs |
Install Node.js + Yarn |
| 5 | fp step mariadb-install |
Install MariaDB + utf8mb4 config |
| 6 | fp step mariadb-secure |
Secure MariaDB root user |
| 7 | fp step redis |
Install Redis server |
| 8 | fp step wkhtmltopdf |
Install wkhtmltopdf + X11 fonts |
| 9 | fp step bench-install |
uv tool install frappe-bench |
| 10 | fp step bench-init |
bench init <name> --frappe-branch ... |
| 11 | fp step site-create |
bench new-site <site> |
| 12 | fp step app-get |
bench get-app <url> |
| 13 | fp step dns-multitenant |
bench config dns_multitenant on |
| 14 | fp step production |
bench setup production + supervisor self-heal + verify |
| 15 | fp step app-install |
bench --site <s> install-app <app> |
| 16 | fp step bench-restart |
supervisorctl reread/update + nginx reload |
| 17 | fp step ssl |
bench setup lets-encrypt <site> |
Add another site to an existing bench
fp step site-create --bench-name my-bench --site-name shop.example.com
fp step dns-multitenant --bench-name my-bench
fp step app-install --bench-name my-bench --site-name shop.example.com --app-url erpnext
fp step bench-restart --bench-name my-bench
fp step ssl --bench-name my-bench --site-name shop.example.com
Set up an automated backup
fp backup setup \
--bench-name my-bench \
--site-name erp.example.com \
--admin-email you@example.com
Service health check
fp service status --bench-name my-bench --site-name erp.example.com
fp service restart
Firewall (UFW)
fp firewall setup # opens 22, 80, 443 by default
Tips for non-fresh VPS
frappe-cli is designed to work on hosts that already have bench installed or already have other Frappe benches:
fp step bench-install— checks ifbenchis onPATH(any of~/.local/bin,/usr/local/bin,/usr/bin) and skips if found.fp step bench-init— skips if the target bench directory already hasapps/frappe/.fp step production— explicitly creates the missingsupervisor.confsymlink that vanillabench setup productionsometimes forgets on multi-bench hosts.fp step ssl— usessudo test -fso it can correctly detect existing certs in the root-owned/etc/letsencrypt/live/directory.
Why frappe-cli over raw bench?
| Pain point | bench alone |
frappe-cli |
|---|---|---|
| Fresh VPS bootstrap | 8+ manual steps, lots of doc-hopping | fp install wizard |
Daily migrate / console |
bench --site long.site.name migrate |
fp migrate |
| Multi-bench supervisor symlink | Sometimes silently missing → errors | Auto-created and verified |
Redis health check before install-app |
None | TCP PING/PONG on queue/cache/socketio ports |
bench setup lets-encrypt prompts |
Two interactive [y/N] prompts |
Automated |
| State across reboots / partial failures | Manual | --resume from saved state |
| Re-running a single step | Find the command, get the args right | fp step <name> |
| Listing sites without SSL | grep + sudo find | fp ssl list |
Configuration file (optional)
For reproducible installs, pass a YAML config with --config:
# production.yaml
system:
timezone: "Africa/Nairobi"
locale: "en_US.UTF-8"
user: "frappe"
frappe:
branch: "version-15"
bench_name: "my-bench"
site_name: "erp.example.com"
admin_password: "changeme"
mariadb_root_password: "changeme"
services:
enable_ssl: true
enable_ufw: true
backup:
admin_email: "you@example.com"
retention_days: 7
See src/frappe_cli/data/example_config.yaml.
Logs and troubleshooting
- All commands log to
/var/log/frappe-installer.log(with local fallback if not writable). - Use
--debugon any command for verbose subprocess output. - Use
--dry-runonstep/installcommands to preview what would run. - For a known-working manual flow you can compare against, see
docs/superpowers/test3-bench-setup.md— a step-by-step runbook used to verify every wizard step.
Development
git clone https://github.com/okama12/frappe-cli.git
cd frappe-cli
# Install dependencies
poetry install
# Run all tests (163 passing)
PYTHONPATH=src poetry run pytest tests/
# Lint (ruff + black --check + isort --check + mypy)
poetry run bash scripts/lint.sh
# Install locally and try it
poetry run fp --help
Project layout
src/frappe_cli/
├── cli.py # root Click group — registers all commands
├── dev/
│ ├── context.py # bench detection + .fp.yaml read/write
│ └── commands.py # use, context, sites + passthrough commands
├── install/
│ ├── wizard.py # fp install wizard
│ ├── context.py # InstallContext dataclass
│ ├── state.py # ~/.frappe-cli-state.json (resume support)
│ └── steps/ # one InstallStep class per wizard step
├── step/ # fp step <name> — thin wrappers around steps/
├── ssl/ # fp ssl setup, fp ssl list
├── site/, app/, service/, backup/, firewall/, ...
└── utils/ # shell, errors, logging, validators
Tests live under tests/ (one file per command group, plus test_install_steps.py for the wizard step classes and test_dev_commands.py for dev workflow).
Contributing
- Fork → feature branch.
- Add or improve a command group / step.
- Add tests in
tests/(useclick.testing.CliRunner). - Run
poetry run bash scripts/lint.shandPYTHONPATH=src poetry run pytest. - Open a PR with a clear description.
About
Built by Rashidi Okama in Tanzania to make day-to-day Frappe work easier.
- Website: https://rashidiokama.com
- GitHub: https://github.com/okama12
Run fp about for the in-CLI credits panel. If this project saves you time, please star the repo — it really helps.
License
MIT © Rashidi Okama
Metadata
Release files for frappe-cli 1.0.23
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| frappe_cli-1.0.23.tar.gz | 94.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| frappe_cli-1.0.23-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 220.0 kB
Release files / frappe_cli-1.0.23.tar.gz
| Download URL | frappe_cli-1.0.23.tar.gz |
|---|---|
| Size | 94.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3bbb4d3cf85137af63feb27fa95e9cb2668a7f97c82782cbc2895329321fad6d
|
|
BLAKE2b-256 checksum How to use checksums |
092ecbccc83818c2417e674134604b3b70daa3e1e3eab9a68242c25daad4481d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / frappe_cli-1.0.23-py3-none-any.whl
| Download URL | frappe_cli-1.0.23-py3-none-any.whl |
|---|---|
| Size | 125.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
46b82761e13d0f2340d040abfa5670cfebdb9d547f2eab85e50a3a3d921fa4e4
|
|
BLAKE2b-256 checksum How to use checksums |
9dcf49fcd55621924f3305a27b490a7f2a066bc4f3f1c23349dad1e0c3d9e717
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|