Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

Frappe / ERPNext v15 — Ubuntu 24.04 LTS Deployment Toolkit

Two single-execution bash scripts that take a fresh Ubuntu 24.04 LTS server from zero to a production-ready Frappe v15 / ERPNext v15 install.

Script Stage What it does
setup_frappe_v15_ubuntu_24.sh System prerequisites Installs git, Python 3.12, MariaDB 10.11, Redis, Node 18 (nvm), Yarn, wkhtmltopdf, frappe-bench
setup_frappe_production.sh Production switch Installs nginx + supervisor + ansible, runs bench setup production ×2, fixes nginx asset perms, verifies services

Between the two scripts you run the standard bench commands (bench init, new-site, get-app, install-app) manually — they're kept out of the scripts so you keep full control over site names, branches, and app selection.


Table of Contents

  1. Requirements
  2. The full deployment flow
  3. Script 1 — setup_frappe_v15_ubuntu_24.sh
  4. Manual middle step — bench init + first site
  5. Script 2 — setup_frappe_production.sh
  6. Post-production hardening
  7. Troubleshooting
  8. Versions installed
  9. Reference

Requirements

  • OS: Ubuntu 24.04 LTS (fresh install recommended)
  • Privilege: A non-root user with sudo (this user will own the bench)
  • Network: Outbound HTTPS to GitHub, PyPI, npm, Ubuntu apt mirrors
  • Resources: Minimum 2 vCPU, 4 GB RAM, 20 GB disk. Recommended for production: 4 vCPU, 8 GB RAM, 40 GB SSD.

⚠️ Never run as the literal root account. Always invoke via sudo from your bench user. Both scripts read $SUDO_USER to know where things like nvm and ~/frappe-bench live. Running as root puts those in /root/..., which bench will never find.


The full deployment flow

┌─────────────────────────────────────────────────────────────────────┐
│  Fresh Ubuntu 24.04 LTS server                                     │
└────────────────────────────────┬────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────────┐
│  1.  setup_frappe_v15_ubuntu_24.sh                                 │
│      Installs all system prerequisites                             │
└────────────────────────────────┬────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────────┐
│  2.  bench init frappe-bench --frappe-branch version-15            │
│      bench new-site <site>                                         │
│      bench get-app erpnext --branch version-15                     │
│      bench --site <site> install-app erpnext                       │
│      (run as the bench user, NOT as root)                          │
└────────────────────────────────┬────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────────┐
│  3.  setup_frappe_production.sh                                    │
│      Switches the bench into production mode                       │
└────────────────────────────────┬────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────────┐
│  4.  (manual) SSL, firewall, backups, patched-Qt wkhtmltopdf       │
└─────────────────────────────────────────────────────────────────────┘

Script 1 — setup_frappe_v15_ubuntu_24.sh

What it installs (STEPS 1–12 of the source guide)

# Step Outcome
1 git Source control for bench and apps
2 python3-dev Python headers for compiling C extensions
3 python3-setuptools, python3-pip Python package management
4 python3.12-venv Virtualenv support for the bench's env/
5 mariadb-server + secure install DB engine, root password set, anonymous users/test DB/remote root removed
6 libmysqlclient-dev MySQL C client headers for Python's mysqlclient
7 50-server.cnf Writes Frappe-required utf8mb4 + barracuda settings, restarts MariaDB
8 redis-server Caching, queues, real-time events
9 nvm + node 18 (LTS) Installed in the non-root user's $HOME/.nvm
10 npm (apt) + yarn (global) Frontend tooling
11 xvfb, libfontconfig, wkhtmltopdf PDF rendering stack (stock — see hardening)
12 frappe-bench bench CLI installed system-wide via pip3 --break-system-packages

What it does not do

  • Run bench init, bench new-site, bench get-app, bench install-app — those are the manual middle step.
  • Install nginx, supervisor, or ansible — those belong to Script 2.
  • Replace stock wkhtmltopdf with the patched-Qt build.

Usage

# Recommended: non-interactive
export MYSQL_ROOT_PASSWORD='YourStrongRootPass'
sudo -E bash setup_frappe_v15_ubuntu_24.sh

# Or interactive (prompts for MariaDB root password)
sudo bash setup_frappe_v15_ubuntu_24.sh

-E preserves your env var so the script can read MYSQL_ROOT_PASSWORD.

MariaDB automation

The script automates mysql_secure_installation with the exact answers from the source guide:

Prompt Applied
Set/change root password ✅ Set to $MYSQL_ROOT_PASSWORD, with unix_socket retained as fallback so local sudo mysql still works
Remove anonymous users ✅
Disallow root login remotely ✅
Remove test database and access to it ✅
Flush privileges ✅

50-server.cnf is overwritten with the Frappe-required stanzas. The original is preserved at /etc/mysql/mariadb.conf.d/50-server.cnf.orig (first run only).


Manual middle step — bench init + first site

Run these as your non-root user (the one that invoked sudo for Script 1):

# 1. Initialize bench (creates ~/frappe-bench)
bench init frappe-bench --frappe-branch version-15
cd frappe-bench

# 2. Create your first site
bench new-site mysite.local
#  → enter the MariaDB root password from Script 1
#  → set the Administrator password

# 3. Pull ERPNext
bench get-app erpnext --branch version-15

# 4. Install ERPNext on the site
bench --site mysite.local install-app erpnext

# 5. (Optional) verify in dev mode
bench start
# → http://mysite.local:8000   (after `bench --site mysite.local add-to-hosts`)

If bench init fails with "Node version >= 18 required", you haven't sourced nvm in this shell. Run source ~/.profile (or log out and back in) and retry.

Once dev mode works, stop bench start (Ctrl+C) before running Script 2.


Script 2 — setup_frappe_production.sh

What it does

  1. Optional — create a new linux user dedicated to running the bench (adduser, usermod -aG sudo, set password).
  2. Validate that the target user exists and their ~/frappe-bench is a real bench (has Procfile + sites/).
  3. Install nginx + supervisor (required by bench setup production).
  4. Install ansible via sudo /usr/bin/python3 -m pip install ansible --break-system-packages — bench setup production invokes ansible playbooks internally and aborts without it.
  5. Run bench setup production <user> --yes twice — first pass writes the nginx + supervisor configs, second pass confirms an idempotent end state (catches the documented first-run quirk where supervisor doesn't reread the new conf file).
  6. Apply chmod o+rx on the user's $HOME and on the bench dir — this is the #1 cause of blank CSS/JS on a fresh production switch, because nginx runs as www-data and can't traverse into sites/assets/ without world-execute on every parent.
  7. nginx -t → systemctl reload nginx → status check.
  8. supervisorctl reread && update && status — full refresh in case the second bench setup production pass added new program groups.

Usage — three modes

Mode 1 — Standard (uses your sudo user, auto-detects ~/frappe-bench)

sudo -E bash setup_frappe_production.sh

Mode 2 — Explicit user + bench path

sudo BENCH_USER=frappe \
     BENCH_PATH=/home/frappe/frappe-bench \
     bash setup_frappe_production.sh

Use this when the bench owner isn't the user invoking sudo (e.g. you provisioned as ubuntu but the bench lives under /home/frappe).

Mode 3 — Create a dedicated bench user first

sudo CREATE_BENCH_USER=yes \
     BENCH_USER=frappe \
     BENCH_PASSWORD='Strong#Pass123' \
     bash setup_frappe_production.sh

This creates the user, adds them to sudo, sets their password, then exits with instructions. You then:

  1. su - frappe
  2. Re-run Script 1 as that user
  3. Run bench init / new-site / etc. as that user
  4. Re-run Script 2 (without CREATE_BENCH_USER) to actually switch to production

Why bench setup production is run twice

A documented gotcha in Frappe production setup: the first invocation may write /etc/supervisor/conf.d/frappe-bench.conf after supervisor has already cached its config view. The second pass forces a clean state — Frappe's own docs and community runbooks call this out. With --yes both passes are non-interactive.

Why chmod o+rx is mandatory

When you visit https://yoursite/assets/frappe/js/desk.min.js, nginx (running as www-data) tries to open /home/<user>/frappe-bench/sites/assets/frappe/js/desk.min.js. For that, every directory in the path needs the execute (x) bit for "others". Without o+rx on /home/<user> and /home/<user>/frappe-bench, you get HTTP 403/404 on every static asset — site loads but is unstyled.


Post-production hardening

Script 2 gets you to a working production bench on HTTP. To complete the stack:

1. Patched-Qt wkhtmltopdf

Ubuntu 24.04's apt wkhtmltopdf is the unpatched-Qt build and produces broken PDFs (page breaks, headers, margins). Replace it:

ARCH=$(dpkg --print-architecture)   # amd64 or arm64
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.jammy_${ARCH}.deb
sudo apt install -y ./wkhtmltox_0.12.6.1-3.jammy_${ARCH}.deb
wkhtmltopdf --version   # must say: with patched qt

2. SSL / HTTPS via Let's Encrypt

sudo snap install --classic certbot
sudo ln -sf /snap/bin/certbot /usr/bin/certbot
cd ~/frappe-bench
sudo bench setup lets-encrypt mysite.example.com

This requires your domain's A record to already point to the server.

3. Firewall

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw --force enable

⚠️ Make sure your SSH session stays alive after enabling UFW. Test by opening a second SSH connection before closing the first.

4. Backups

# One-off
bench --site mysite.example.com backup --with-files

# Recurring — bench already adds a daily job to the user's crontab via
#   bench setup backups
# Confirm:
crontab -l -u $USER

For offsite backups, configure S3:

bench --site mysite.example.com set-config backup_path_db    "s3://yourbucket/db"
bench --site mysite.example.com set-config backup_path_files "s3://yourbucket/files"
bench setup backups

5. Force nvm Node for the bench user's non-login shells

If you ever invoke bench from cron or a non-login context and hit node: command not found, append to ~/.bashrc:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm use default >/dev/null

Troubleshooting

Script 1 issues

mysql --protocol=socket -uroot fails during step 5

MariaDB didn't start cleanly. Investigate:

sudo systemctl status mariadb
sudo journalctl -u mariadb --no-pager | tail -50

bench: command not found after step 12

pip3 install ... --break-system-packages placed it in /usr/local/bin. Run hash -r or open a fresh shell.

nvm: command not found after the script finishes

Expected — nvm sources only in login shells. Run source ~/.profile or log out and back in.

bench init fails with Node version >= 18 required

You're not sourcing nvm. Either log out/in, or:

export NVM_DIR="$HOME/.nvm" && . "$NVM_DIR/nvm.sh" && nvm use default

Script 2 issues

bench setup production exits with Cannot find executable 'ansible-playbook'

Shouldn't happen — Script 2 installs ansible before invoking bench. If you see this, the install silently failed. Re-run manually:

sudo /usr/bin/python3 -m pip install ansible --break-system-packages
which ansible-playbook

Site loads but CSS/JS are blank (no styling)

Nginx can't traverse to /assets/*. Re-apply:

sudo chmod o+rx /home/$USER
sudo chmod o+rx /home/$USER/frappe-bench
sudo systemctl reload nginx

supervisorctl status shows frappe-bench-*:* FATAL

Workers crashed on startup. Check:

sudo tail -100 ~/frappe-bench/logs/*.log
sudo supervisorctl tail frappe-bench-frappe-web stderr

Common causes: site_config wrong, MariaDB not reachable, port 9000 conflict (socketio).

Nginx returns 502 Bad Gateway

Gunicorn isn't running. Verify:

sudo supervisorctl status frappe-bench-frappe-web
sudo supervisorctl restart frappe-bench-frappe-web

Re-running Script 2 leaves stale supervisor groups

After a clean state change (renamed bench, changed user), purge before re-running:

sudo rm /etc/supervisor/conf.d/frappe-bench.conf
sudo rm /etc/nginx/conf.d/frappe-bench.conf
sudo supervisorctl reread && sudo supervisorctl update
sudo bash setup_frappe_production.sh

MariaDB password recovery

sudo systemctl stop mariadb
sudo mysqld_safe --skip-grant-tables --skip-networking &
mysql -uroot
# > FLUSH PRIVILEGES;
# > ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPassword';
# > EXIT;
sudo killall mysqld
sudo systemctl start mariadb

Versions installed

Component Version on Ubuntu 24.04 Source
Python 3.12.x apt
MariaDB 10.11.x apt
Redis 7.0.x apt
Node.js 18.x (latest LTS patch) nvm
Yarn 1.22.x npm i -g yarn
wkhtmltopdf 0.12.6 (apt — unpatched Qt) apt
frappe-bench latest from PyPI pip3
nginx 1.24.x apt (Script 2)
supervisor 4.2.x apt (Script 2)
ansible latest from PyPI pip3 (Script 2)

Frappe v15 will warn that MariaDB 10.11 is "more than 10.8 which is not yet tested." In practice 10.11 is widely used in production with v15 — the warning is benign.


Reference


License

Use, modify, and redistribute freely. No warranty.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages