| name | frappe-ops-deployment |
| description | Use when deploying Frappe/ERPNext to production, configuring Nginx or Supervisor, setting up Docker, enabling SSL, or hardening security. Prevents insecure deployments, missing reverse proxy config, and broken process management. Covers production setup, Nginx configuration, Supervisor/systemd, Docker Compose, Let's Encrypt SSL, firewall rules, security hardening. Keywords: deployment, production, nginx, supervisor, docker, ssl, letsencrypt, security, gunicorn, systemd, go live, production setup, HTTPS setup, server config, deploy to VPS, Docker setup..
|
| license | MIT |
| compatibility | Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16. |
| metadata | {"author":"OpenAEC-Foundation","version":"2.0"} |
Production Deployment
Deploy Frappe/ERPNext to production using either traditional (bench + Nginx + Supervisor) or Docker (frappe_docker + Compose). Frappe officially recommends Docker for new deployments.
Quick Reference
sudo bench setup production [frappe-user]
bench setup supervisor
bench setup nginx
bench setup sudoers $(whoami)
sudo ln -s $(pwd)/config/supervisor.conf /etc/supervisor/conf.d/frappe-bench.conf
sudo ln -s $(pwd)/config/nginx.conf /etc/nginx/conf.d/frappe-bench.conf
sudo -H bench setup lets-encrypt [site-name]
sudo -H bench setup lets-encrypt [site-name] --custom-domain [domain]
bench config dns_multitenant on
bench setup nginx
sudo service nginx reload
Deployment Decision Tree
Which deployment method?
|
+-- New server, minimal ops experience?
| +-- Docker (frappe_docker) — recommended by Frappe
|
+-- Existing server with bench already installed?
| +-- Traditional (bench setup production)
|
+-- Need custom Frappe apps or complex build?
| +-- Docker with custom image build
|
+-- Cloud hosting (AWS/GCP/Azure)?
| +-- Docker on VM or Kubernetes
| +-- OR Frappe Cloud (managed)
|
+-- Single site or multi-site?
| +-- Single site: standard setup
| +-- Multi-site: DNS multitenancy required
Traditional Deployment
Process Architecture
Internet → Nginx (port 80/443)
|
+-- Static files served directly
+-- /api, /app → Gunicorn (port 8000)
+-- /socket.io → Node.js socketio (port 9000)
Supervisor manages:
- frappe-bench-web (gunicorn)
- frappe-bench-socketio (node)
- frappe-bench-worker-short
- frappe-bench-worker-default
- frappe-bench-worker-long
- frappe-bench-redis-cache
- frappe-bench-redis-queue
- frappe-bench-schedule (scheduler)
Step-by-Step Setup
sudo pip3 install frappe-bench
bench init frappe-bench --frappe-branch version-15
cd frappe-bench
bench new-site mysite.example.com
bench --site mysite.example.com install-app erpnext
sudo bench setup production $(whoami)
sudo supervisorctl status
sudo nginx -t && sudo systemctl reload nginx
Nginx Configuration
bench setup nginx generates config/nginx.conf with:
- Server block per site (DNS multitenancy)
- Proxy to gunicorn on port 8000
- WebSocket proxy to socketio on port 9000
- Static file serving from
sites/ directory
- Client max body size (default varies by version)
ALWAYS disable default nginx site to avoid port 80 conflicts:
sudo rm /etc/nginx/sites-enabled/default
Supervisor Configuration
bench setup supervisor generates config/supervisor.conf with:
--skip-redis flag to skip Redis if managed externally
For CentOS/RHEL: use .ini extension instead of .conf for supervisor configs.
SSL / HTTPS
Let's Encrypt (Recommended)
sudo -H bench setup lets-encrypt mysite.example.com
sudo -H bench setup lets-encrypt mysite.example.com --custom-domain www.example.com
sudo bench renew-lets-encrypt
Prerequisites:
- DNS multitenancy enabled (
bench config dns_multitenant on)
- Domain resolves to server IP
- Port 80 open for ACME challenge
- Root/sudo access
Certificate locations: /etc/letsencrypt/live/example.com/
fullchain.pem — certificate + chain
privkey.pem — private key
Certificates expire every 90 days. The setup command adds a monthly cron for renewal.
Custom SSL Certificate
sudo mkdir -p /etc/nginx/conf.d/ssl
sudo cp certificate.crt /etc/nginx/conf.d/ssl/
sudo cp private.key /etc/nginx/conf.d/ssl/
sudo chmod 600 /etc/nginx/conf.d/ssl/private.key
bench set-config ssl_certificate "/etc/nginx/conf.d/ssl/certificate.crt"
bench set-config ssl_certificate_key "/etc/nginx/conf.d/ssl/private.key"
bench setup nginx
sudo systemctl reload nginx
All HTTP traffic is automatically redirected to HTTPS after SSL is configured.
DNS Multitenancy (Multi-Site)
bench config dns_multitenant on
bench new-site site1.example.com
bench new-site site2.example.com
bench setup nginx
sudo systemctl reload nginx
ALWAYS use the actual domain as the site name. Nginx routes requests to the correct site based on the Host header.
Docker Deployment
Architecture (frappe_docker)
Docker Compose Services:
- configurator — initializes DB/Redis config (runs once)
- backend — Frappe/ERPNext application server (gunicorn)
- frontend — Nginx reverse proxy
- websocket — Node.js Socket.IO server
- queue-short — RQ worker for short jobs
- queue-long — RQ worker for long jobs
- (external) — MariaDB/PostgreSQL + Redis (separate containers or managed)
Shared Volume:
- sites:/home/frappe/frappe-bench/sites (persistent data)
Production Docker Compose
git clone https://github.com/frappe/frappe_docker.git
cd frappe_docker
docker compose -f compose.yaml up -d
Custom Image Build
export APPS_JSON='[
{"url":"https://github.com/frappe/erpnext","branch":"version-15"},
{"url":"https://github.com/your-org/custom-app","branch":"main"}
]'
docker build \
--build-arg APPS_JSON_BASE64=$(echo $APPS_JSON | base64 -w 0) \
--tag your-registry/custom-erpnext:latest \
images/custom/
Security Hardening
Firewall (UFW)
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Fail2Ban
sudo apt install fail2ban
sudo systemctl enable fail2ban
Redis Authentication
bench create-rq-users --set-admin-password
Additional Hardening
- ALWAYS disable root SSH login (
PermitRootLogin no in /etc/ssh/sshd_config)
- ALWAYS use SSH key authentication, disable password auth
- NEVER run bench as root — create a dedicated
frappe user
- ALWAYS keep system packages updated (
sudo apt update && sudo apt upgrade)
- ALWAYS set
ALLOW_CORS only to trusted domains in site_config.json
Zero-Downtime Updates
bench update --pull --patch --build --requirements
docker compose pull
docker compose up -d --no-deps backend websocket queue-short queue-long
docker compose exec backend bench --site mysite.example.com migrate
Version Differences
| Feature | v14 | v15 | v16 |
|---|
| Docker recommended | No | Official recommendation | Yes |
create-rq-users | No | Yes | Yes |
| ARM64 Docker images | No | Yes | Yes |
| Site-level logs | v13+ | Yes | Yes |
extend_doctype_class | No | No | Yes |
Reference Files
Related Skills
frappe-ops-backup — Backup and disaster recovery
frappe-ops-performance — Performance tuning
frappe-ops-bench — Bench CLI reference
frappe-ops-upgrades — Version upgrade procedures